From 0e78341102e32499835ad99bbfd1f51e7f334e9b Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 25 Jul 2025 20:29:50 +0200 Subject: [PATCH] Trim whitespace from nicknames to prevent display issues - Added didSet observer to nickname property to trim on every change - Trim nickname when loading from UserDefaults - Update validateAndSaveNickname to properly trim - Trim received nicknames in announce packets - Add custom init/decoder for NoiseIdentityAnnouncement to ensure trimming - Trim nicknames when decoding from binary data --- bitchat/Protocols/BitchatProtocol.swift | 22 +++++++++++++++++++-- bitchat/Services/BluetoothMeshService.swift | 4 +++- bitchat/ViewModels/ChatViewModel.swift | 22 +++++++++++++++++---- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index 2eafa8a7..ce78b360 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -654,12 +654,27 @@ struct NoiseIdentityAnnouncement: Codable { self.peerID = peerID self.publicKey = publicKey self.signingPublicKey = signingPublicKey - self.nickname = nickname + // Trim whitespace from nickname + self.nickname = nickname.trimmingCharacters(in: .whitespacesAndNewlines) self.timestamp = timestamp self.previousPeerID = previousPeerID self.signature = signature } + // Custom decoder to ensure nickname is trimmed + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.peerID = try container.decode(String.self, forKey: .peerID) + self.publicKey = try container.decode(Data.self, forKey: .publicKey) + self.signingPublicKey = try container.decode(Data.self, forKey: .signingPublicKey) + // Trim whitespace from decoded nickname + let rawNickname = try container.decode(String.self, forKey: .nickname) + self.nickname = rawNickname.trimmingCharacters(in: .whitespacesAndNewlines) + self.timestamp = try container.decode(Date.self, forKey: .timestamp) + self.previousPeerID = try container.decodeIfPresent(String.self, forKey: .previousPeerID) + self.signature = try container.decode(Data.self, forKey: .signature) + } + func encode() -> Data? { return try? JSONEncoder().encode(self) } @@ -738,9 +753,12 @@ struct NoiseIdentityAnnouncement: Codable { guard let publicKey = dataCopy.readData(at: &offset), let signingPublicKey = dataCopy.readData(at: &offset), - let nickname = dataCopy.readString(at: &offset), + let rawNickname = dataCopy.readString(at: &offset), let timestamp = dataCopy.readDate(at: &offset) else { return nil } + // Trim whitespace from nickname + let nickname = rawNickname.trimmingCharacters(in: .whitespacesAndNewlines) + var previousPeerID: String? = nil if hasPreviousPeerID { // Read previousPeerID using safe method diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index d5a20bb0..f80e56bd 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -2592,7 +2592,9 @@ class BluetoothMeshService: NSObject { // Note: 0x02 was legacy keyExchange - removed case .announce: - if let nickname = String(data: packet.payload, encoding: .utf8) { + if let rawNickname = String(data: packet.payload, encoding: .utf8) { + // Trim whitespace from received nickname + let nickname = rawNickname.trimmingCharacters(in: .whitespacesAndNewlines) let senderID = packet.senderID.hexEncodedString() SecureLogger.log("handleReceivedPacket: Received announce from \(senderID), peripheral is \(peripheral != nil ? "present" : "nil")", category: SecureLogger.session, level: .debug) diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index c00468aa..ce85d8d4 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -25,7 +25,15 @@ class ChatViewModel: ObservableObject { private var pendingPrivateMessages: [String: [BitchatMessage]] = [:] // peerID -> messages private var messageBatchTimer: Timer? private let messageBatchInterval: TimeInterval = 0.1 // 100ms batching window - @Published var nickname: String = "" + @Published var nickname: String = "" { + didSet { + // Trim whitespace whenever nickname is set + let trimmed = nickname.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed != nickname { + nickname = trimmed + } + } + } @Published var isConnected = false @Published var privateChats: [String: [BitchatMessage]] = [:] // peerID -> messages @Published var selectedPrivateChatPeer: String? = nil @@ -176,7 +184,8 @@ class ChatViewModel: ObservableObject { private func loadNickname() { if let savedNickname = userDefaults.string(forKey: nicknameKey) { - nickname = savedNickname + // Trim whitespace when loading + nickname = savedNickname.trimmingCharacters(in: .whitespacesAndNewlines) } else { nickname = "anon\(Int.random(in: 1000...9999))" saveNickname() @@ -192,9 +201,14 @@ class ChatViewModel: ObservableObject { } func validateAndSaveNickname() { - // Check if nickname is empty or just whitespace - if nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + // Trim whitespace from nickname + let trimmed = nickname.trimmingCharacters(in: .whitespacesAndNewlines) + + // Check if nickname is empty after trimming + if trimmed.isEmpty { nickname = "anon\(Int.random(in: 1000...9999))" + } else { + nickname = trimmed } saveNickname() }