mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 15:05:20 +00:00
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>
This commit is contained in:
@@ -18,7 +18,6 @@ struct BitchatPeer: Identifiable, Equatable {
|
|||||||
// Connection state
|
// Connection state
|
||||||
enum ConnectionState {
|
enum ConnectionState {
|
||||||
case bluetoothConnected
|
case bluetoothConnected
|
||||||
case relayConnected // Connected via mesh relay (another peer)
|
|
||||||
case nostrAvailable // Mutual favorite, reachable via Nostr
|
case nostrAvailable // Mutual favorite, reachable via Nostr
|
||||||
case offline // Not connected via any transport
|
case offline // Not connected via any transport
|
||||||
}
|
}
|
||||||
@@ -26,8 +25,6 @@ struct BitchatPeer: Identifiable, Equatable {
|
|||||||
var connectionState: ConnectionState {
|
var connectionState: ConnectionState {
|
||||||
if isConnected {
|
if isConnected {
|
||||||
return .bluetoothConnected
|
return .bluetoothConnected
|
||||||
} else if isRelayConnected {
|
|
||||||
return .relayConnected
|
|
||||||
} else if favoriteStatus?.isMutual == true {
|
} else if favoriteStatus?.isMutual == true {
|
||||||
// Mutual favorites can communicate via Nostr when offline
|
// Mutual favorites can communicate via Nostr when offline
|
||||||
return .nostrAvailable
|
return .nostrAvailable
|
||||||
@@ -36,8 +33,6 @@ struct BitchatPeer: Identifiable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRelayConnected: Bool = false // Set by PeerManager based on session state
|
|
||||||
|
|
||||||
var isFavorite: Bool {
|
var isFavorite: Bool {
|
||||||
favoriteStatus?.isFavorite ?? false
|
favoriteStatus?.isFavorite ?? false
|
||||||
}
|
}
|
||||||
@@ -59,8 +54,6 @@ struct BitchatPeer: Identifiable, Equatable {
|
|||||||
switch connectionState {
|
switch connectionState {
|
||||||
case .bluetoothConnected:
|
case .bluetoothConnected:
|
||||||
return "📻" // Radio icon for mesh connection
|
return "📻" // Radio icon for mesh connection
|
||||||
case .relayConnected:
|
|
||||||
return "🔗" // Chain link for relay connection
|
|
||||||
case .nostrAvailable:
|
case .nostrAvailable:
|
||||||
return "🌐" // Purple globe for Nostr
|
return "🌐" // Purple globe for Nostr
|
||||||
case .offline:
|
case .offline:
|
||||||
@@ -78,15 +71,13 @@ struct BitchatPeer: Identifiable, Equatable {
|
|||||||
noisePublicKey: Data,
|
noisePublicKey: Data,
|
||||||
nickname: String,
|
nickname: String,
|
||||||
lastSeen: Date = Date(),
|
lastSeen: Date = Date(),
|
||||||
isConnected: Bool = false,
|
isConnected: Bool = false
|
||||||
isRelayConnected: Bool = false
|
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.noisePublicKey = noisePublicKey
|
self.noisePublicKey = noisePublicKey
|
||||||
self.nickname = nickname
|
self.nickname = nickname
|
||||||
self.lastSeen = lastSeen
|
self.lastSeen = lastSeen
|
||||||
self.isConnected = isConnected
|
self.isConnected = isConnected
|
||||||
self.isRelayConnected = isRelayConnected
|
|
||||||
|
|
||||||
// Load favorite status - will be set later by the manager
|
// Load favorite status - will be set later by the manager
|
||||||
self.favoriteStatus = nil
|
self.favoriteStatus = nil
|
||||||
@@ -156,28 +147,15 @@ class PeerManager: ObservableObject {
|
|||||||
continue
|
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 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)
|
// Skip disconnected peers unless they're favorites (handled later)
|
||||||
if !isConnected && !isRelayConnected {
|
if !isConnected {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if isConnected || isRelayConnected {
|
if isConnected {
|
||||||
connectedNicknames.insert(nickname)
|
connectedNicknames.insert(nickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +166,7 @@ class PeerManager: ObservableObject {
|
|||||||
id: peerID,
|
id: peerID,
|
||||||
noisePublicKey: noiseKey,
|
noisePublicKey: noiseKey,
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
isConnected: isConnected,
|
isConnected: isConnected
|
||||||
isRelayConnected: isRelayConnected
|
|
||||||
)
|
)
|
||||||
// Set favorite status - check both by current noise key and by nickname
|
// Set favorite status - check both by current noise key and by nickname
|
||||||
if let favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey) {
|
if let favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey) {
|
||||||
@@ -211,14 +188,14 @@ class PeerManager: ObservableObject {
|
|||||||
allPeers.append(peer)
|
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 {
|
for (favoriteKey, favorite) in favoritesService.favorites {
|
||||||
let favoriteID = favorite.peerNoisePublicKey.hexEncodedString()
|
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) {
|
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)
|
category: SecureLogger.session, level: .debug)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -259,16 +236,12 @@ class PeerManager: ObservableObject {
|
|||||||
!(peer.displayName == "Unknown" && peer.favoriteStatus == nil)
|
!(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
|
allPeers.sort { lhs, rhs in
|
||||||
// Direct connections first
|
// Direct connections first
|
||||||
if lhs.isConnected != rhs.isConnected {
|
if lhs.isConnected != rhs.isConnected {
|
||||||
return lhs.isConnected
|
return lhs.isConnected
|
||||||
}
|
}
|
||||||
// Then relay connections
|
|
||||||
if lhs.isRelayConnected != rhs.isRelayConnected {
|
|
||||||
return lhs.isRelayConnected
|
|
||||||
}
|
|
||||||
// Then favorites
|
// Then favorites
|
||||||
if lhs.isFavorite != rhs.isFavorite {
|
if lhs.isFavorite != rhs.isFavorite {
|
||||||
return lhs.isFavorite
|
return lhs.isFavorite
|
||||||
@@ -321,13 +294,10 @@ class PeerManager: ObservableObject {
|
|||||||
|
|
||||||
// Log each peer's status
|
// Log each peer's status
|
||||||
for peer in allPeers {
|
for peer in allPeers {
|
||||||
// Use the actual statusIcon from the peer which accounts for relay connections
|
|
||||||
let statusIcon: String
|
let statusIcon: String
|
||||||
switch peer.connectionState {
|
switch peer.connectionState {
|
||||||
case .bluetoothConnected:
|
case .bluetoothConnected:
|
||||||
statusIcon = "🟢"
|
statusIcon = "🟢"
|
||||||
case .relayConnected:
|
|
||||||
statusIcon = "🔗"
|
|
||||||
case .nostrAvailable:
|
case .nostrAvailable:
|
||||||
statusIcon = "🌐"
|
statusIcon = "🌐"
|
||||||
case .offline:
|
case .offline:
|
||||||
|
|||||||
@@ -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] {
|
func getEstablishedSessions() -> [String: NoiseSession] {
|
||||||
return managerQueue.sync {
|
return managerQueue.sync {
|
||||||
return sessions.filter { $0.value.isEstablished() }
|
return sessions.filter { $0.value.isEstablished() }
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ struct NostrProtocol {
|
|||||||
|
|
||||||
// 2. Create ephemeral key for this message
|
// 2. Create ephemeral key for this message
|
||||||
let ephemeralKey = try P256K.Schnorr.PrivateKey()
|
let ephemeralKey = try P256K.Schnorr.PrivateKey()
|
||||||
let _ = Data(ephemeralKey.xonly.bytes).hexEncodedString()
|
|
||||||
// Created ephemeral key for seal
|
// Created ephemeral key for seal
|
||||||
|
|
||||||
// 3. Seal the rumor (encrypt to recipient)
|
// 3. Seal the rumor (encrypt to recipient)
|
||||||
@@ -213,7 +212,6 @@ struct NostrProtocol {
|
|||||||
throw NostrError.invalidPublicKey
|
throw NostrError.invalidPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = Data(senderKey.xonly.bytes).hexEncodedString()
|
|
||||||
// Encrypting message
|
// Encrypting message
|
||||||
|
|
||||||
// Derive shared secret
|
// Derive shared secret
|
||||||
@@ -447,14 +445,11 @@ struct NostrProtocol {
|
|||||||
|
|
||||||
// Log with explicit UTC and local time for debugging
|
// Log with explicit UTC and local time for debugging
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
|
// Removed unnecessary date formatting operations
|
||||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||||
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
||||||
let _ = formatter.string(from: now)
|
|
||||||
let _ = formatter.string(from: randomized)
|
|
||||||
|
|
||||||
formatter.timeZone = TimeZone.current
|
formatter.timeZone = TimeZone.current
|
||||||
let _ = formatter.string(from: now)
|
|
||||||
let _ = formatter.string(from: randomized)
|
|
||||||
|
|
||||||
// Timestamp randomized for privacy
|
// Timestamp randomized for privacy
|
||||||
|
|
||||||
|
|||||||
@@ -273,8 +273,7 @@ class NostrRelayManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "EOSE":
|
case "EOSE":
|
||||||
if array.count >= 2,
|
if array.count >= 2 {
|
||||||
let _ = array[1] as? String {
|
|
||||||
// End of stored events
|
// End of stored events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,8 +289,8 @@ class NostrRelayManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "NOTICE":
|
case "NOTICE":
|
||||||
if array.count >= 2,
|
if array.count >= 2 {
|
||||||
let _ = array[1] as? String {
|
// Server notice received
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -705,7 +705,6 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Update PeerSession
|
// Update PeerSession
|
||||||
if let session = peerSessions[peerID] {
|
if let session = peerSessions[peerID] {
|
||||||
let _ = session.isConnected
|
|
||||||
session.updateBluetoothConnection(peripheral: mapping.peripheral, characteristic: nil)
|
session.updateBluetoothConnection(peripheral: mapping.peripheral, characteristic: nil)
|
||||||
} else {
|
} else {
|
||||||
// This is a truly new peer session
|
// This is a truly new peer session
|
||||||
@@ -2804,7 +2803,6 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Check if we have a recent entry
|
// Check if we have a recent entry
|
||||||
if let entry = protocolMessageDedup[key], !isExpired(entry) {
|
if let entry = protocolMessageDedup[key], !isExpired(entry) {
|
||||||
let _ = Date().timeIntervalSince(entry.sentAt)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3466,7 +3464,6 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Bloom filter will be reset by timer, processedMessages is now bounded
|
// 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
|
// Note: We'll decode messages in the switch statement below, not here
|
||||||
@@ -4548,7 +4545,6 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
if !isPeerIDOurs(senderID) {
|
if !isPeerIDOurs(senderID) {
|
||||||
// Check our current handshake state
|
// Check our current handshake state
|
||||||
let _ = handshakeCoordinator.getHandshakeState(for: senderID)
|
|
||||||
// Processing handshake response
|
// Processing handshake response
|
||||||
|
|
||||||
// Process the response - this could be message 2 or message 3 in the XX pattern
|
// 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) {
|
private func handleFragment(_ packet: BitchatPacket, from peerID: String) {
|
||||||
@@ -5093,8 +5088,6 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
category: SecureLogger.session, level: .info)
|
category: SecureLogger.session, level: .info)
|
||||||
|
|
||||||
// Log current peripheral mappings
|
// Log current peripheral mappings
|
||||||
let _ = collectionsQueue.sync { peripheralMappings.count }
|
|
||||||
let _ = connectionPool.count
|
|
||||||
|
|
||||||
peripheral.delegate = self
|
peripheral.delegate = self
|
||||||
peripheral.discoverServices([BluetoothMeshService.serviceUUID])
|
peripheral.discoverServices([BluetoothMeshService.serviceUUID])
|
||||||
@@ -5282,8 +5275,6 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
// Time tracking removed - now in PeerSession
|
// Time tracking removed - now in PeerSession
|
||||||
// Time tracking removed - now in PeerSession
|
// Time tracking removed - now in PeerSession
|
||||||
// Keep lastSuccessfulMessageTime to validate session on reconnect
|
// Keep lastSuccessfulMessageTime to validate session on reconnect
|
||||||
let lastSuccess = self.peerSessions[peerID]?.lastSuccessfulMessageTime ?? Date.distantPast
|
|
||||||
let _ = Date().timeIntervalSince(lastSuccess)
|
|
||||||
// Keeping Noise session on disconnect
|
// 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
|
// 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()
|
let packetSenderID = packet.senderID.hexEncodedString()
|
||||||
|
|
||||||
// Log the packet type we received
|
// Log the packet type we received
|
||||||
@@ -6611,8 +6601,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
SecureLogger.logHandshake("processing \(isInitiation ? "init" : "response")", peerID: peerID, success: true)
|
SecureLogger.logHandshake("processing \(isInitiation ? "init" : "response")", peerID: peerID, success: true)
|
||||||
|
|
||||||
// Get current handshake state before processing
|
// Get current handshake state before processing
|
||||||
let _ = handshakeCoordinator.getHandshakeState(for: peerID)
|
|
||||||
let _ = noiseService.hasEstablishedSession(with: peerID)
|
|
||||||
// Current handshake state check
|
// Current handshake state check
|
||||||
|
|
||||||
// Check for duplicate handshake messages
|
// Check for duplicate handshake messages
|
||||||
@@ -7535,7 +7523,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
self.pendingPrivateMessages[recipientPeerID] = []
|
self.pendingPrivateMessages[recipientPeerID] = []
|
||||||
}
|
}
|
||||||
self.pendingPrivateMessages[recipientPeerID]?.append((content, recipientNickname, messageID ?? UUID().uuidString))
|
self.pendingPrivateMessages[recipientPeerID]?.append((content, recipientNickname, messageID ?? UUID().uuidString))
|
||||||
let _ = self.pendingPrivateMessages[recipientPeerID]?.count ?? 0
|
|
||||||
// Queued private message
|
// Queued private message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,73 +17,7 @@ class KeychainManager {
|
|||||||
private let service = "chat.bitchat"
|
private let service = "chat.bitchat"
|
||||||
private let appGroup = "group.chat.bitchat"
|
private let appGroup = "group.chat.bitchat"
|
||||||
|
|
||||||
private init() {
|
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 func isSandboxed() -> Bool {
|
private func isSandboxed() -> Bool {
|
||||||
@@ -240,11 +174,6 @@ class KeychainManager {
|
|||||||
return status == errSecSuccess || status == errSecItemNotFound
|
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
|
// Delete ALL keychain data for panic mode
|
||||||
func deleteAllKeychainData() -> Bool {
|
func deleteAllKeychainData() -> Bool {
|
||||||
@@ -384,76 +313,4 @@ class KeychainManager {
|
|||||||
let key = "identity_noiseStaticKey"
|
let key = "identity_noiseStaticKey"
|
||||||
return retrieveData(forKey: key) != nil
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -419,24 +419,6 @@ class NoiseEncryptionService {
|
|||||||
SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID))
|
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
|
// MARK: - Private Helpers
|
||||||
|
|
||||||
private func handleSessionEstablished(peerID: String, remoteStaticKey: Curve25519.KeyAgreement.PublicKey) {
|
private func handleSessionEstablished(peerID: String, remoteStaticKey: Curve25519.KeyAgreement.PublicKey) {
|
||||||
|
|||||||
@@ -737,13 +737,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Check if the recipient is blocked
|
// Check if the recipient is blocked
|
||||||
if isPeerBlocked(peerID) {
|
if isPeerBlocked(peerID) {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot send message to \(recipientNickname): user is blocked.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot send message to \(recipientNickname): user is blocked.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,13 +797,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
category: SecureLogger.session, level: .warning)
|
category: SecureLogger.session, level: .warning)
|
||||||
|
|
||||||
// Add system message to inform user
|
// Add system message to inform user
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("Cannot send message to \(recipientNickname) - peer is not reachable via mesh or Nostr.")
|
||||||
sender: "system",
|
|
||||||
content: "Cannot send message to \(recipientNickname) - peer is not reachable via mesh or Nostr.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
addMessage(systemMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,27 +852,15 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Check if the peer is blocked
|
// Check if the peer is blocked
|
||||||
if isPeerBlocked(peerID) {
|
if isPeerBlocked(peerID) {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot start chat with \(peerNickname): user is blocked.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot start chat with \(peerNickname): user is blocked.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a moon peer (we favorite them but they don't favorite us) AND they're offline
|
// 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
|
// Only require mutual favorites for offline Nostr messaging
|
||||||
if let peer = peerIndex[peerID],
|
if let peer = peerIndex[peerID],
|
||||||
peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected && !peer.isRelayConnected {
|
peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot start chat with \(peerNickname): mutual favorite required for offline messaging.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot start chat with \(peerNickname): mutual favorite required for offline messaging.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,10 +1011,8 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleDeliveryAcknowledgment(_ notification: Notification) {
|
@objc private func handleDeliveryAcknowledgment(_ notification: Notification) {
|
||||||
guard let messageId = notification.userInfo?["messageId"] as? String,
|
guard let messageId = notification.userInfo?["messageId"] as? String else { return }
|
||||||
let senderNoiseKey = notification.userInfo?["senderNoiseKey"] as? Data else { return }
|
|
||||||
|
|
||||||
let _ = senderNoiseKey.hexEncodedString()
|
|
||||||
|
|
||||||
|
|
||||||
// Update the delivery status for the message
|
// Update the delivery status for the message
|
||||||
@@ -1500,10 +1474,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
privateChats.removeAll()
|
privateChats.removeAll()
|
||||||
unreadPrivateMessages.removeAll()
|
unreadPrivateMessages.removeAll()
|
||||||
|
|
||||||
// First run aggressive cleanup to get rid of all legacy items
|
// Delete all keychain data
|
||||||
_ = KeychainManager.shared.aggressiveCleanupLegacyItems()
|
|
||||||
|
|
||||||
// Then delete all current keychain data
|
|
||||||
_ = KeychainManager.shared.deleteAllKeychainData()
|
_ = KeychainManager.shared.deleteAllKeychainData()
|
||||||
|
|
||||||
// Clear UserDefaults identity fallbacks
|
// Clear UserDefaults identity fallbacks
|
||||||
@@ -2323,54 +2294,24 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
let messageContent = parts[2...].joined(separator: " ")
|
let messageContent = parts[2...].joined(separator: " ")
|
||||||
sendPrivateMessage(messageContent, to: peerID)
|
sendPrivateMessage(messageContent, to: peerID)
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("started private chat with \(nickname)")
|
||||||
sender: "system",
|
|
||||||
content: "started private chat with \(nickname)",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("user '\(nickname)' not found. they may be offline or using a different nickname.")
|
||||||
sender: "system",
|
|
||||||
content: "user '\(nickname)' not found. they may be offline or using a different nickname.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("usage: /m @nickname [message] or /m nickname [message]")
|
||||||
sender: "system",
|
|
||||||
content: "usage: /m @nickname [message] or /m nickname [message]",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
case "/w":
|
case "/w":
|
||||||
let peerNicknames = meshService.getPeerNicknames()
|
let peerNicknames = meshService.getPeerNicknames()
|
||||||
if connectedPeers.isEmpty {
|
if connectedPeers.isEmpty {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("no one else is online right now.")
|
||||||
sender: "system",
|
|
||||||
content: "no one else is online right now.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
} else {
|
} else {
|
||||||
let onlineList = connectedPeers.compactMap { peerID in
|
let onlineList = connectedPeers.compactMap { peerID in
|
||||||
peerNicknames[peerID]
|
peerNicknames[peerID]
|
||||||
}.sorted().joined(separator: ", ")
|
}.sorted().joined(separator: ", ")
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("online users: \(onlineList)")
|
||||||
sender: "system",
|
|
||||||
content: "online users: \(onlineList)",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
case "/clear":
|
case "/clear":
|
||||||
// Clear messages based on current context
|
// Clear messages based on current context
|
||||||
@@ -2493,13 +2434,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if let fingerprintStr = meshService.getPeerFingerprint(peerID) {
|
if let fingerprintStr = meshService.getPeerFingerprint(peerID) {
|
||||||
|
|
||||||
if SecureIdentityStateManager.shared.isBlocked(fingerprint: fingerprintStr) {
|
if SecureIdentityStateManager.shared.isBlocked(fingerprint: fingerprintStr) {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("\(nickname) is already blocked.")
|
||||||
sender: "system",
|
|
||||||
content: "\(nickname) is already blocked.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
} else {
|
} else {
|
||||||
// Update or create social identity with blocked status
|
// Update or create social identity with blocked status
|
||||||
if var identity = SecureIdentityStateManager.shared.getSocialIdentity(for: fingerprintStr) {
|
if var identity = SecureIdentityStateManager.shared.getSocialIdentity(for: fingerprintStr) {
|
||||||
@@ -2523,42 +2458,18 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
blockedUsers.insert(fingerprintStr)
|
blockedUsers.insert(fingerprintStr)
|
||||||
favoritePeers.remove(fingerprintStr)
|
favoritePeers.remove(fingerprintStr)
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("blocked \(nickname). you will no longer receive messages from them.")
|
||||||
sender: "system",
|
|
||||||
content: "blocked \(nickname). you will no longer receive messages from them.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot block \(nickname): unable to verify identity.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot block \(nickname): unable to verify identity.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot block \(nickname): user not found.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot block \(nickname): user not found.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// List blocked users
|
// List blocked users
|
||||||
if blockedUsers.isEmpty {
|
if blockedUsers.isEmpty {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("no blocked peers.")
|
||||||
sender: "system",
|
|
||||||
content: "no blocked peers.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
} else {
|
} else {
|
||||||
// Find nicknames for blocked users
|
// Find nicknames for blocked users
|
||||||
var blockedNicknames: [String] = []
|
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 blockedList = blockedNicknames.isEmpty ? "blocked peers (not currently online)" : blockedNicknames.sorted().joined(separator: ", ")
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("blocked peers: \(blockedList)")
|
||||||
sender: "system",
|
|
||||||
content: "blocked peers: \(blockedList)",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2601,48 +2506,18 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// Update local set for UI
|
// Update local set for UI
|
||||||
blockedUsers.remove(fingerprintStr)
|
blockedUsers.remove(fingerprintStr)
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("unblocked \(nickname).")
|
||||||
sender: "system",
|
|
||||||
content: "unblocked \(nickname).",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("\(nickname) is not blocked.")
|
||||||
sender: "system",
|
|
||||||
content: "\(nickname) is not blocked.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot unblock \(nickname): unable to verify identity.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot unblock \(nickname): unable to verify identity.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("cannot unblock \(nickname): user not found.")
|
||||||
sender: "system",
|
|
||||||
content: "cannot unblock \(nickname): user not found.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("usage: /unblock <nickname>")
|
||||||
sender: "system",
|
|
||||||
content: "usage: /unblock <nickname>",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "/fav":
|
case "/fav":
|
||||||
@@ -2671,31 +2546,13 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: true)
|
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("added \(nickname) to favorites.")
|
||||||
sender: "system",
|
|
||||||
content: "added \(nickname) to favorites.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("can't find peer: \(nickname)")
|
||||||
sender: "system",
|
|
||||||
content: "can't find peer: \(nickname)",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("usage: /fav <nickname>")
|
||||||
sender: "system",
|
|
||||||
content: "usage: /fav <nickname>",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "/unfav":
|
case "/unfav":
|
||||||
@@ -2718,41 +2575,17 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: false)
|
try? await self?.messageRouter?.sendFavoriteNotification(to: noisePublicKey, isFavorite: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("removed \(nickname) from favorites.")
|
||||||
sender: "system",
|
|
||||||
content: "removed \(nickname) from favorites.",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("can't find peer: \(nickname)")
|
||||||
sender: "system",
|
|
||||||
content: "can't find peer: \(nickname)",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("usage: /unfav <nickname>")
|
||||||
sender: "system",
|
|
||||||
content: "usage: /unfav <nickname>",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "/testnostr":
|
case "/testnostr":
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("testing nostr relay connectivity...")
|
||||||
sender: "system",
|
|
||||||
content: "testing nostr relay connectivity...",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
if let relayManager = self.nostrRelayManager {
|
if let relayManager = self.nostrRelayManager {
|
||||||
@@ -2788,13 +2621,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// Unknown command
|
// Unknown command
|
||||||
let systemMessage = BitchatMessage(
|
addSystemMessage("unknown command: \(cmd).")
|
||||||
sender: "system",
|
|
||||||
content: "unknown command: \(cmd).",
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
messages.append(systemMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
} // End of ChatViewModel class
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ struct ContentView: View {
|
|||||||
ForEach(windowedMessages, id: \.id) { message in
|
ForEach(windowedMessages, id: \.id) { message in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
// Check if current user is mentioned
|
// Check if current user is mentioned
|
||||||
let _ = message.mentions?.contains(viewModel.nickname) ?? false
|
|
||||||
|
|
||||||
if message.sender == "system" {
|
if message.sender == "system" {
|
||||||
// System messages
|
// System messages
|
||||||
@@ -681,12 +680,6 @@ struct ContentView: View {
|
|||||||
.font(.system(size: 10))
|
.font(.system(size: 10))
|
||||||
.foregroundColor(textColor)
|
.foregroundColor(textColor)
|
||||||
.accessibilityLabel("Connected via mesh")
|
.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:
|
case .nostrAvailable:
|
||||||
// Purple globe for mutual favorites reachable via Nostr
|
// Purple globe for mutual favorites reachable via Nostr
|
||||||
Image(systemName: "globe")
|
Image(systemName: "globe")
|
||||||
@@ -902,7 +895,7 @@ struct ContentView: View {
|
|||||||
let peerCounts = viewModel.allPeers.reduce(into: (others: 0, mesh: 0)) { counts, peer in
|
let peerCounts = viewModel.allPeers.reduce(into: (others: 0, mesh: 0)) { counts, peer in
|
||||||
guard peer.id != viewModel.meshService.myPeerID else { return }
|
guard peer.id != viewModel.meshService.myPeerID else { return }
|
||||||
|
|
||||||
let isMeshConnected = peer.isConnected || peer.isRelayConnected
|
let isMeshConnected = peer.isConnected
|
||||||
if isMeshConnected {
|
if isMeshConnected {
|
||||||
counts.mesh += 1
|
counts.mesh += 1
|
||||||
counts.others += 1
|
counts.others += 1
|
||||||
@@ -994,12 +987,6 @@ struct ContentView: View {
|
|||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.foregroundColor(textColor)
|
.foregroundColor(textColor)
|
||||||
.accessibilityLabel("Connected via mesh")
|
.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:
|
case .nostrAvailable:
|
||||||
// Purple globe for Nostr
|
// Purple globe for Nostr
|
||||||
Image(systemName: "globe")
|
Image(systemName: "globe")
|
||||||
|
|||||||
@@ -138,44 +138,6 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
|
|
||||||
// MARK: - Helper Methods
|
// 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) {
|
private func saveToSharedDefaults(content: String, type: String) {
|
||||||
// Use app groups to share data between extension and main app
|
// Use app groups to share data between extension and main app
|
||||||
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
|
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -226,23 +226,6 @@ final class NoiseProtocolTests: XCTestCase {
|
|||||||
XCTAssertEqual(decrypted, plaintext)
|
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
|
// MARK: - Security Tests
|
||||||
|
|
||||||
func testTamperedCiphertextDetection() throws {
|
func testTamperedCiphertextDetection() throws {
|
||||||
|
|||||||
Reference in New Issue
Block a user