diff --git a/bitchat/Models/BitchatMessage.swift b/bitchat/Models/BitchatMessage.swift new file mode 100644 index 00000000..1ae62040 --- /dev/null +++ b/bitchat/Models/BitchatMessage.swift @@ -0,0 +1,343 @@ +// +// BitchatMessage.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation + +/// Represents a user-visible message in the BitChat system. +/// Handles both broadcast messages and private encrypted messages, +/// with support for mentions, replies, and delivery tracking. +/// - Note: This is the primary data model for chat messages +final class BitchatMessage: Codable { + let id: String + let sender: String + let content: String + let timestamp: Date + let isRelay: Bool + let originalSender: String? + let isPrivate: Bool + let recipientNickname: String? + let senderPeerID: String? + let mentions: [String]? // Array of mentioned nicknames + var deliveryStatus: DeliveryStatus? // Delivery tracking + + // Cached formatted text (not included in Codable) + private var _cachedFormattedText: [String: AttributedString] = [:] + + func getCachedFormattedText(isDark: Bool, isSelf: Bool) -> AttributedString? { + return _cachedFormattedText["\(isDark)-\(isSelf)"] + } + + func setCachedFormattedText(_ text: AttributedString, isDark: Bool, isSelf: Bool) { + _cachedFormattedText["\(isDark)-\(isSelf)"] = text + } + + // Codable implementation + enum CodingKeys: String, CodingKey { + case id, sender, content, timestamp, isRelay, originalSender + case isPrivate, recipientNickname, senderPeerID, mentions, deliveryStatus + } + + init(id: String? = nil, sender: String, content: String, timestamp: Date, isRelay: Bool, originalSender: String? = nil, isPrivate: Bool = false, recipientNickname: String? = nil, senderPeerID: String? = nil, mentions: [String]? = nil, deliveryStatus: DeliveryStatus? = nil) { + self.id = id ?? UUID().uuidString + self.sender = sender + self.content = content + self.timestamp = timestamp + self.isRelay = isRelay + self.originalSender = originalSender + self.isPrivate = isPrivate + self.recipientNickname = recipientNickname + self.senderPeerID = senderPeerID + self.mentions = mentions + self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil) + } +} + +// MARK: - Equatable Conformance + +extension BitchatMessage: Equatable { + static func == (lhs: BitchatMessage, rhs: BitchatMessage) -> Bool { + return lhs.id == rhs.id && + lhs.sender == rhs.sender && + lhs.content == rhs.content && + lhs.timestamp == rhs.timestamp && + lhs.isRelay == rhs.isRelay && + lhs.originalSender == rhs.originalSender && + lhs.isPrivate == rhs.isPrivate && + lhs.recipientNickname == rhs.recipientNickname && + lhs.senderPeerID == rhs.senderPeerID && + lhs.mentions == rhs.mentions && + lhs.deliveryStatus == rhs.deliveryStatus + } +} + +// MARK: - Binary encoding + +extension BitchatMessage { + func toBinaryPayload() -> Data? { + var data = Data() + + // Message format: + // - Flags: 1 byte (bit 0: isRelay, bit 1: isPrivate, bit 2: hasOriginalSender, bit 3: hasRecipientNickname, bit 4: hasSenderPeerID, bit 5: hasMentions) + // - Timestamp: 8 bytes (seconds since epoch) + // - ID length: 1 byte + // - ID: variable + // - Sender length: 1 byte + // - Sender: variable + // - Content length: 2 bytes + // - Content: variable + // Optional fields based on flags: + // - Original sender length + data + // - Recipient nickname length + data + // - Sender peer ID length + data + // - Mentions array + + var flags: UInt8 = 0 + if isRelay { flags |= 0x01 } + if isPrivate { flags |= 0x02 } + if originalSender != nil { flags |= 0x04 } + if recipientNickname != nil { flags |= 0x08 } + if senderPeerID != nil { flags |= 0x10 } + if mentions != nil && !mentions!.isEmpty { flags |= 0x20 } + + data.append(flags) + + // Timestamp (in milliseconds) + let timestampMillis = UInt64(timestamp.timeIntervalSince1970 * 1000) + // Encode as 8 bytes, big-endian + for i in (0..<8).reversed() { + data.append(UInt8((timestampMillis >> (i * 8)) & 0xFF)) + } + + // ID + if let idData = id.data(using: .utf8) { + data.append(UInt8(min(idData.count, 255))) + data.append(idData.prefix(255)) + } else { + data.append(0) + } + + // Sender + if let senderData = sender.data(using: .utf8) { + data.append(UInt8(min(senderData.count, 255))) + data.append(senderData.prefix(255)) + } else { + data.append(0) + } + + // Content + if let contentData = content.data(using: .utf8) { + let length = UInt16(min(contentData.count, 65535)) + // Encode length as 2 bytes, big-endian + data.append(UInt8((length >> 8) & 0xFF)) + data.append(UInt8(length & 0xFF)) + data.append(contentData.prefix(Int(length))) + } else { + data.append(contentsOf: [0, 0]) + } + + // Optional fields + if let originalSender = originalSender, let origData = originalSender.data(using: .utf8) { + data.append(UInt8(min(origData.count, 255))) + data.append(origData.prefix(255)) + } + + if let recipientNickname = recipientNickname, let recipData = recipientNickname.data(using: .utf8) { + data.append(UInt8(min(recipData.count, 255))) + data.append(recipData.prefix(255)) + } + + if let senderPeerID = senderPeerID, let peerData = senderPeerID.data(using: .utf8) { + data.append(UInt8(min(peerData.count, 255))) + data.append(peerData.prefix(255)) + } + + // Mentions array + if let mentions = mentions { + data.append(UInt8(min(mentions.count, 255))) // Number of mentions + for mention in mentions.prefix(255) { + if let mentionData = mention.data(using: .utf8) { + data.append(UInt8(min(mentionData.count, 255))) + data.append(mentionData.prefix(255)) + } else { + data.append(0) + } + } + } + + + return data + } + + convenience init?(_ data: Data) { + // Create an immutable copy to prevent threading issues + let dataCopy = Data(data) + + + guard dataCopy.count >= 13 else { + return nil + } + + var offset = 0 + + // Flags + guard offset < dataCopy.count else { + return nil + } + let flags = dataCopy[offset]; offset += 1 + let isRelay = (flags & 0x01) != 0 + let isPrivate = (flags & 0x02) != 0 + let hasOriginalSender = (flags & 0x04) != 0 + let hasRecipientNickname = (flags & 0x08) != 0 + let hasSenderPeerID = (flags & 0x10) != 0 + let hasMentions = (flags & 0x20) != 0 + + // Timestamp + guard offset + 8 <= dataCopy.count else { + return nil + } + let timestampData = dataCopy[offset.. 0 { + mentions = [] + for _ in 0.. [Element] { + let arr = filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false } + guard arr.count > 1 else { + return arr + } + var seen = Set() + var dedup: [BitchatMessage] = [] + for m in arr.sorted(by: { $0.timestamp < $1.timestamp }) { + if !seen.contains(m.id) { + dedup.append(m) + seen.insert(m.id) + } + } + return dedup + } +} diff --git a/bitchat/Protocols/BinaryProtocol.swift b/bitchat/Protocols/BinaryProtocol.swift index d6a0b37b..390ec6ef 100644 --- a/bitchat/Protocols/BinaryProtocol.swift +++ b/bitchat/Protocols/BinaryProtocol.swift @@ -328,236 +328,3 @@ struct BinaryProtocol { } } } - -// Binary encoding for BitchatMessage -extension BitchatMessage { - func toBinaryPayload() -> Data? { - var data = Data() - - // Message format: - // - Flags: 1 byte (bit 0: isRelay, bit 1: isPrivate, bit 2: hasOriginalSender, bit 3: hasRecipientNickname, bit 4: hasSenderPeerID, bit 5: hasMentions) - // - Timestamp: 8 bytes (seconds since epoch) - // - ID length: 1 byte - // - ID: variable - // - Sender length: 1 byte - // - Sender: variable - // - Content length: 2 bytes - // - Content: variable - // Optional fields based on flags: - // - Original sender length + data - // - Recipient nickname length + data - // - Sender peer ID length + data - // - Mentions array - - var flags: UInt8 = 0 - if isRelay { flags |= 0x01 } - if isPrivate { flags |= 0x02 } - if originalSender != nil { flags |= 0x04 } - if recipientNickname != nil { flags |= 0x08 } - if senderPeerID != nil { flags |= 0x10 } - if mentions != nil && !mentions!.isEmpty { flags |= 0x20 } - - data.append(flags) - - // Timestamp (in milliseconds) - let timestampMillis = UInt64(timestamp.timeIntervalSince1970 * 1000) - // Encode as 8 bytes, big-endian - for i in (0..<8).reversed() { - data.append(UInt8((timestampMillis >> (i * 8)) & 0xFF)) - } - - // ID - if let idData = id.data(using: .utf8) { - data.append(UInt8(min(idData.count, 255))) - data.append(idData.prefix(255)) - } else { - data.append(0) - } - - // Sender - if let senderData = sender.data(using: .utf8) { - data.append(UInt8(min(senderData.count, 255))) - data.append(senderData.prefix(255)) - } else { - data.append(0) - } - - // Content - if let contentData = content.data(using: .utf8) { - let length = UInt16(min(contentData.count, 65535)) - // Encode length as 2 bytes, big-endian - data.append(UInt8((length >> 8) & 0xFF)) - data.append(UInt8(length & 0xFF)) - data.append(contentData.prefix(Int(length))) - } else { - data.append(contentsOf: [0, 0]) - } - - // Optional fields - if let originalSender = originalSender, let origData = originalSender.data(using: .utf8) { - data.append(UInt8(min(origData.count, 255))) - data.append(origData.prefix(255)) - } - - if let recipientNickname = recipientNickname, let recipData = recipientNickname.data(using: .utf8) { - data.append(UInt8(min(recipData.count, 255))) - data.append(recipData.prefix(255)) - } - - if let senderPeerID = senderPeerID, let peerData = senderPeerID.data(using: .utf8) { - data.append(UInt8(min(peerData.count, 255))) - data.append(peerData.prefix(255)) - } - - // Mentions array - if let mentions = mentions { - data.append(UInt8(min(mentions.count, 255))) // Number of mentions - for mention in mentions.prefix(255) { - if let mentionData = mention.data(using: .utf8) { - data.append(UInt8(min(mentionData.count, 255))) - data.append(mentionData.prefix(255)) - } else { - data.append(0) - } - } - } - - - return data - } - - static func fromBinaryPayload(_ data: Data) -> BitchatMessage? { - // Create an immutable copy to prevent threading issues - let dataCopy = Data(data) - - - guard dataCopy.count >= 13 else { - return nil - } - - var offset = 0 - - // Flags - guard offset < dataCopy.count else { - return nil - } - let flags = dataCopy[offset]; offset += 1 - let isRelay = (flags & 0x01) != 0 - let isPrivate = (flags & 0x02) != 0 - let hasOriginalSender = (flags & 0x04) != 0 - let hasRecipientNickname = (flags & 0x08) != 0 - let hasSenderPeerID = (flags & 0x10) != 0 - let hasMentions = (flags & 0x20) != 0 - - // Timestamp - guard offset + 8 <= dataCopy.count else { - return nil - } - let timestampData = dataCopy[offset.. 0 { - mentions = [] - for _ in 0.. AttributedString? { - return _cachedFormattedText["\(isDark)-\(isSelf)"] - } - - func setCachedFormattedText(_ text: AttributedString, isDark: Bool, isSelf: Bool) { - _cachedFormattedText["\(isDark)-\(isSelf)"] = text - } - - // Codable implementation - enum CodingKeys: String, CodingKey { - case id, sender, content, timestamp, isRelay, originalSender - case isPrivate, recipientNickname, senderPeerID, mentions, deliveryStatus - } - - init(id: String? = nil, sender: String, content: String, timestamp: Date, isRelay: Bool, originalSender: String? = nil, isPrivate: Bool = false, recipientNickname: String? = nil, senderPeerID: String? = nil, mentions: [String]? = nil, deliveryStatus: DeliveryStatus? = nil) { - self.id = id ?? UUID().uuidString - self.sender = sender - self.content = content - self.timestamp = timestamp - self.isRelay = isRelay - self.originalSender = originalSender - self.isPrivate = isPrivate - self.recipientNickname = recipientNickname - self.senderPeerID = senderPeerID - self.mentions = mentions - self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil) - } -} - -// Equatable conformance for BitchatMessage -extension BitchatMessage: Equatable { - static func == (lhs: BitchatMessage, rhs: BitchatMessage) -> Bool { - return lhs.id == rhs.id && - lhs.sender == rhs.sender && - lhs.content == rhs.content && - lhs.timestamp == rhs.timestamp && - lhs.isRelay == rhs.isRelay && - lhs.originalSender == rhs.originalSender && - lhs.isPrivate == rhs.isPrivate && - lhs.recipientNickname == rhs.recipientNickname && - lhs.senderPeerID == rhs.senderPeerID && - lhs.mentions == rhs.mentions && - lhs.deliveryStatus == rhs.deliveryStatus - } -} - // MARK: - Delegate Protocol protocol BitchatDelegate: AnyObject { diff --git a/bitchat/Utils/String+Nickname.swift b/bitchat/Utils/String+Nickname.swift new file mode 100644 index 00000000..b586aa66 --- /dev/null +++ b/bitchat/Utils/String+Nickname.swift @@ -0,0 +1,25 @@ +// +// String+Nickname.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation + +extension String { + /// Split a nickname into base and a '#abcd' suffix if present + func splitSuffix() -> (String, String) { + let name = self.replacingOccurrences(of: "@", with: "") + guard name.count >= 5 else { return (name, "") } + let suffix = String(name.suffix(5)) + if suffix.first == "#", suffix.dropFirst().allSatisfy({ c in + ("0"..."9").contains(String(c)) || ("a"..."f").contains(String(c)) || ("A"..."F").contains(String(c)) + }) { + let base = String(name.dropLast(5)) + return (base, suffix) + } + return (name, "") + } +} diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 6063aa8d..03dda589 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -1522,29 +1522,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { geohashPeople = [] teleportedGeo.removeAll() case .location(let ch): - // Sanitize existing timeline (filter any prior empty-content entries) - var arr = geoTimelines[ch.geohash] ?? [] - arr.removeAll { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - // Deduplicate by ID while preserving order (from oldest to newest) - if arr.count > 1 { - var seen = Set() - var dedup: [BitchatMessage] = [] - for m in arr.sorted(by: { $0.timestamp < $1.timestamp }) { - if !seen.contains(m.id) { - dedup.append(m) - seen.insert(m.id) - } - } - arr = dedup - } // Persist the cleaned/sorted timeline for this geohash - geoTimelines[ch.geohash] = arr - messages = arr - // Debug: log if any empty messages are present post-sanitize - let emptyGeo = messages.filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count - if emptyGeo > 0 { - SecureLogger.debug("RenderGuard: geohash \(ch.geohash) timeline has \(emptyGeo) empty messages after sanitize", category: .session) - } + let deduped = geoTimelines[ch.geohash]?.cleanedAndDeduped() ?? [] + geoTimelines[ch.geohash] = deduped + messages = deduped } // If switching to a location channel, flush any pending geohash-only system messages if case .location = channel, !pendingGeohashSystemMessages.isEmpty { @@ -3088,17 +3069,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } - - - // MARK: - Formatting Helpers - - func formatTimestamp(_ date: Date) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss" - return formatter.string(from: date) - } - - // MARK: - Autocomplete func updateAutocomplete(for text: String, cursorPosition: Int) { @@ -3160,87 +3130,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // MARK: - Message Formatting - func getSenderColor(for message: BitchatMessage, colorScheme: ColorScheme) -> Color { - let isDark = colorScheme == .dark - let primaryColor = isDark ? Color.green : Color(red: 0, green: 0.5, blue: 0) - - return primaryColor - } - - - func formatMessageContent(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString { - let isDark = colorScheme == .dark - let contentText = message.content - var processedContent = AttributedString() - - // Regular expressions for mentions and hashtags - let mentionPattern = "@([\\p{L}0-9_]+)" - let hashtagPattern = "#([a-zA-Z0-9_]+)" - - let mentionRegex = try? NSRegularExpression(pattern: mentionPattern, options: []) - let hashtagRegex = try? NSRegularExpression(pattern: hashtagPattern, options: []) - - let nsContent = contentText as NSString - let nsLen = nsContent.length - let mentionMatches = mentionRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: nsLen)) ?? [] - let hashtagMatches = hashtagRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: nsLen)) ?? [] - - // Combine and sort all matches - var allMatches: [(range: NSRange, type: String)] = [] - for match in mentionMatches { - allMatches.append((match.range(at: 0), "mention")) - } - for match in hashtagMatches { - allMatches.append((match.range(at: 0), "hashtag")) - } - allMatches.sort { $0.range.location < $1.range.location } - - var lastEndIndex = contentText.startIndex - - for (matchRange, matchType) in allMatches { - // Add text before the match - if let range = Range(matchRange, in: contentText) { - if lastEndIndex < range.lowerBound { - let beforeText = String(contentText[lastEndIndex.. AttributedString { // Determine if this message was sent by self (mesh, geo, or DM) @@ -3272,7 +3161,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if message.sender != "system" { // Sender (at the beginning) with light-gray suffix styling if present - let (baseName, suffix) = splitSuffix(from: message.sender) + let (baseName, suffix) = message.sender.splitSuffix() var senderStyle = AttributeContainer() // Use consistent color for all senders senderStyle.foregroundColor = baseColor @@ -3423,7 +3312,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let matchText = String(content[nsRange]) if type == "mention" { // Split optional '#abcd' suffix and color suffix light grey - let (mBase, mSuffix) = splitSuffix(from: matchText.replacingOccurrences(of: "@", with: "")) + let (mBase, mSuffix) = matchText.splitSuffix() // Determine if this mention targets me (resolves with optional suffix per active channel) let mySuffix: String? = { if case .location(let ch) = activeChannel, let id = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) { @@ -3547,7 +3436,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Add timestamp at the end (smaller, light grey) - let timestamp = AttributedString(" [\(formatTimestamp(message.timestamp))]") + let timestamp = AttributedString(" [\(message.formattedTimestamp)]") var timestampStyle = AttributeContainer() timestampStyle.foregroundColor = Color.gray.opacity(0.7) timestampStyle.font = .system(size: 10, design: .monospaced) @@ -3561,7 +3450,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { result.append(content.mergingAttributes(contentStyle)) // Add timestamp at the end for system messages too - let timestamp = AttributedString(" [\(formatTimestamp(message.timestamp))]") + let timestamp = AttributedString(" [\(message.formattedTimestamp)]") var timestampStyle = AttributeContainer() timestampStyle.foregroundColor = Color.gray.opacity(0.5) timestampStyle.font = .system(size: 10, design: .monospaced) @@ -3573,19 +3462,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { return result } - - // Split a nickname into base and a '#abcd' suffix if present - private func splitSuffix(from name: String) -> (String, String) { - guard name.count >= 5 else { return (name, "") } - let suffix = String(name.suffix(5)) - if suffix.first == "#", suffix.dropFirst().allSatisfy({ c in - ("0"..."9").contains(String(c)) || ("a"..."f").contains(String(c)) || ("A"..."F").contains(String(c)) - }) { - let base = String(name.dropLast(5)) - return (base, suffix) - } - return (name, "") - } func formatMessage(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString { var result = AttributedString() @@ -3601,7 +3477,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { result.append(content.mergingAttributes(contentStyle)) // Add timestamp at the end for system messages - let timestamp = AttributedString(" [\(formatTimestamp(message.timestamp))]") + let timestamp = AttributedString(" [\(message.formattedTimestamp)]") var timestampStyle = AttributeContainer() timestampStyle.foregroundColor = Color.gray.opacity(0.5) timestampStyle.font = .system(size: 10, design: .monospaced) @@ -3675,7 +3551,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Add timestamp at the end (smaller, light grey) - let timestamp = AttributedString(" [\(formatTimestamp(message.timestamp))]") + let timestamp = AttributedString(" [\(message.formattedTimestamp)]") var timestampStyle = AttributeContainer() timestampStyle.foregroundColor = Color.gray.opacity(0.7) timestampStyle.font = .system(size: 10, design: .monospaced) diff --git a/bitchatTests/BLEServiceTests.swift b/bitchatTests/BLEServiceTests.swift index 8148c0f0..ee2a58a8 100644 --- a/bitchatTests/BLEServiceTests.swift +++ b/bitchatTests/BLEServiceTests.swift @@ -215,7 +215,7 @@ final class BLEServiceTests: XCTestCase { let expectation = XCTestExpectation(description: "Delivery handler called") service.packetDeliveryHandler = { packet in - if let msg = BitchatMessage.fromBinaryPayload(packet.payload) { + if let msg = BitchatMessage(packet.payload) { XCTAssertEqual(msg.content, "Test delivery") expectation.fulfill() } diff --git a/bitchatTests/EndToEnd/PrivateChatE2ETests.swift b/bitchatTests/EndToEnd/PrivateChatE2ETests.swift index 0c90e5ab..a1176ac4 100644 --- a/bitchatTests/EndToEnd/PrivateChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PrivateChatE2ETests.swift @@ -139,7 +139,7 @@ final class PrivateChatE2ETests: XCTestCase { alice.packetDeliveryHandler = { packet in // Encrypt outgoing private messages if packet.type == 0x01, - let message = BitchatMessage.fromBinaryPayload(packet.payload), + let message = BitchatMessage(packet.payload), message.isPrivate { do { let encrypted = try aliceManager.encrypt(packet.payload, for: TestConstants.testPeerID2) @@ -164,7 +164,7 @@ final class PrivateChatE2ETests: XCTestCase { if packet.type == 0x02 { do { let decrypted = try bobManager.decrypt(packet.payload, from: TestConstants.testPeerID1) - if let message = BitchatMessage.fromBinaryPayload(decrypted) { + if let message = BitchatMessage(decrypted) { XCTAssertEqual(message.content, TestConstants.testMessage1) XCTAssertTrue(message.isPrivate) expectation.fulfill() diff --git a/bitchatTests/EndToEnd/PublicChatE2ETests.swift b/bitchatTests/EndToEnd/PublicChatE2ETests.swift index 59106150..1df9ff7a 100644 --- a/bitchatTests/EndToEnd/PublicChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PublicChatE2ETests.swift @@ -98,7 +98,7 @@ final class PublicChatE2ETests: XCTestCase { // Set up relay in Bob bob.packetDeliveryHandler = { packet in // Bob should relay to Charlie - if let message = BitchatMessage.fromBinaryPayload(packet.payload), + if let message = BitchatMessage(packet.payload), message.sender == TestConstants.testNickname1 { // Create relay message @@ -437,7 +437,7 @@ final class PublicChatE2ETests: XCTestCase { // Check if should relay guard packet.ttl > 1 else { return } - if let message = BitchatMessage.fromBinaryPayload(packet.payload) { + if let message = BitchatMessage(packet.payload) { // Don't relay own messages guard message.senderPeerID != node.peerID else { return } diff --git a/bitchatTests/Integration/IntegrationTests.swift b/bitchatTests/Integration/IntegrationTests.swift index 8824f657..076dd281 100644 --- a/bitchatTests/Integration/IntegrationTests.swift +++ b/bitchatTests/Integration/IntegrationTests.swift @@ -531,7 +531,7 @@ final class IntegrationTests: XCTestCase { // Setup encryption at Alice nodes["Alice"]!.packetDeliveryHandler = { packet in if packet.type == 0x01, - let message = BitchatMessage.fromBinaryPayload(packet.payload), + let message = BitchatMessage(packet.payload), message.isPrivate && packet.recipientID != nil { // Encrypt private messages if let encrypted = try? self.noiseManagers["Alice"]!.encrypt(packet.payload, for: TestConstants.testPeerID2) { @@ -553,7 +553,7 @@ final class IntegrationTests: XCTestCase { nodes["Bob"]!.packetDeliveryHandler = { packet in if packet.type == 0x02 { if let decrypted = try? self.noiseManagers["Bob"]!.decrypt(packet.payload, from: TestConstants.testPeerID1), - let message = BitchatMessage.fromBinaryPayload(decrypted) { + let message = BitchatMessage(decrypted) { bobDecrypted = message.content == "Secret message" expectation.fulfill() } @@ -626,7 +626,7 @@ final class IntegrationTests: XCTestCase { node.packetDeliveryHandler = { packet in guard packet.ttl > 1 else { return } - if let message = BitchatMessage.fromBinaryPayload(packet.payload) { + if let message = BitchatMessage(packet.payload) { guard message.senderPeerID != node.peerID else { return } let relayMessage = BitchatMessage( diff --git a/bitchatTests/Mocks/MockBLEService.swift b/bitchatTests/Mocks/MockBLEService.swift index ca545c50..ce2730b0 100644 --- a/bitchatTests/Mocks/MockBLEService.swift +++ b/bitchatTests/Mocks/MockBLEService.swift @@ -309,7 +309,7 @@ final class MockBLEService: NSObject { func simulateIncomingPacket(_ packet: BitchatPacket) { // Process through the actual handling logic - if let message = BitchatMessage.fromBinaryPayload(packet.payload) { + if let message = BitchatMessage(packet.payload) { var shouldDeliver = false seenLock.lock() if !seenMessageIDs.contains(message.id) { diff --git a/bitchatTests/Protocol/BinaryProtocolTests.swift b/bitchatTests/Protocol/BinaryProtocolTests.swift index 96636ee5..3ea6d163 100644 --- a/bitchatTests/Protocol/BinaryProtocolTests.swift +++ b/bitchatTests/Protocol/BinaryProtocolTests.swift @@ -197,7 +197,7 @@ final class BinaryProtocolTests: XCTestCase { return } - guard let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + guard let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to decode message from binary") return } @@ -219,7 +219,7 @@ final class BinaryProtocolTests: XCTestCase { ) guard let payload = message.toBinaryPayload(), - let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to encode/decode private message") return } @@ -233,7 +233,7 @@ final class BinaryProtocolTests: XCTestCase { let message = TestHelpers.createTestMessage(mentions: mentions) guard let payload = message.toBinaryPayload(), - let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to encode/decode message with mentions") return } @@ -256,7 +256,7 @@ final class BinaryProtocolTests: XCTestCase { ) guard let payload = message.toBinaryPayload(), - let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to encode/decode relay message") return } @@ -294,7 +294,7 @@ final class BinaryProtocolTests: XCTestCase { let message = TestHelpers.createTestMessage(content: largeContent) guard let payload = message.toBinaryPayload(), - let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to handle large message") return } @@ -307,7 +307,7 @@ final class BinaryProtocolTests: XCTestCase { let emptyMessage = TestHelpers.createTestMessage(content: "") guard let payload = emptyMessage.toBinaryPayload(), - let decodedMessage = BitchatMessage.fromBinaryPayload(payload) else { + let decodedMessage = BitchatMessage(payload) else { XCTFail("Failed to handle empty message") return }