Compare commits

...
7 Commits
11 changed files with 656 additions and 459 deletions
+23 -10
View File
@@ -21,7 +21,7 @@ final class BitchatMessage: Codable {
let originalSender: String? let originalSender: String?
let isPrivate: Bool let isPrivate: Bool
let recipientNickname: String? let recipientNickname: String?
let senderPeerID: String? let senderPeer: Peer?
let mentions: [String]? // Array of mentioned nicknames let mentions: [String]? // Array of mentioned nicknames
var deliveryStatus: DeliveryStatus? // Delivery tracking var deliveryStatus: DeliveryStatus? // Delivery tracking
@@ -39,10 +39,23 @@ final class BitchatMessage: Codable {
// Codable implementation // Codable implementation
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id, sender, content, timestamp, isRelay, originalSender case id, sender, content, timestamp, isRelay, originalSender
case isPrivate, recipientNickname, senderPeerID, mentions, deliveryStatus case isPrivate, recipientNickname, mentions, deliveryStatus
case senderPeer = "senderPeerID" // backwards compatibility
} }
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) { init(
id: String? = nil,
sender: String,
content: String,
timestamp: Date,
isRelay: Bool,
originalSender: String? = nil,
isPrivate: Bool = false,
recipientNickname: String? = nil,
senderPeer: Peer? = nil,
mentions: [String]? = nil,
deliveryStatus: DeliveryStatus? = nil
) {
self.id = id ?? UUID().uuidString self.id = id ?? UUID().uuidString
self.sender = sender self.sender = sender
self.content = content self.content = content
@@ -51,7 +64,7 @@ final class BitchatMessage: Codable {
self.originalSender = originalSender self.originalSender = originalSender
self.isPrivate = isPrivate self.isPrivate = isPrivate
self.recipientNickname = recipientNickname self.recipientNickname = recipientNickname
self.senderPeerID = senderPeerID self.senderPeer = senderPeer
self.mentions = mentions self.mentions = mentions
self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil) self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil)
} }
@@ -69,7 +82,7 @@ extension BitchatMessage: Equatable {
lhs.originalSender == rhs.originalSender && lhs.originalSender == rhs.originalSender &&
lhs.isPrivate == rhs.isPrivate && lhs.isPrivate == rhs.isPrivate &&
lhs.recipientNickname == rhs.recipientNickname && lhs.recipientNickname == rhs.recipientNickname &&
lhs.senderPeerID == rhs.senderPeerID && lhs.senderPeer == rhs.senderPeer &&
lhs.mentions == rhs.mentions && lhs.mentions == rhs.mentions &&
lhs.deliveryStatus == rhs.deliveryStatus lhs.deliveryStatus == rhs.deliveryStatus
} }
@@ -101,7 +114,7 @@ extension BitchatMessage {
if isPrivate { flags |= 0x02 } if isPrivate { flags |= 0x02 }
if originalSender != nil { flags |= 0x04 } if originalSender != nil { flags |= 0x04 }
if recipientNickname != nil { flags |= 0x08 } if recipientNickname != nil { flags |= 0x08 }
if senderPeerID != nil { flags |= 0x10 } if senderPeer != nil { flags |= 0x10 }
if mentions != nil && !mentions!.isEmpty { flags |= 0x20 } if mentions != nil && !mentions!.isEmpty { flags |= 0x20 }
data.append(flags) data.append(flags)
@@ -151,7 +164,7 @@ extension BitchatMessage {
data.append(recipData.prefix(255)) data.append(recipData.prefix(255))
} }
if let senderPeerID = senderPeerID, let peerData = senderPeerID.data(using: .utf8) { if let peerData = senderPeer?.data {
data.append(UInt8(min(peerData.count, 255))) data.append(UInt8(min(peerData.count, 255)))
data.append(peerData.prefix(255)) data.append(peerData.prefix(255))
} }
@@ -264,11 +277,11 @@ extension BitchatMessage {
} }
} }
var senderPeerID: String? var senderPeer: Peer?
if hasSenderPeerID && offset < dataCopy.count { if hasSenderPeerID && offset < dataCopy.count {
let length = Int(dataCopy[offset]); offset += 1 let length = Int(dataCopy[offset]); offset += 1
if offset + length <= dataCopy.count { if offset + length <= dataCopy.count {
senderPeerID = String(data: dataCopy[offset..<offset+length], encoding: .utf8) senderPeer = Peer(data: dataCopy[offset..<offset+length])
offset += length offset += length
} }
} }
@@ -302,7 +315,7 @@ extension BitchatMessage {
originalSender: originalSender, originalSender: originalSender,
isPrivate: isPrivate, isPrivate: isPrivate,
recipientNickname: recipientNickname, recipientNickname: recipientNickname,
senderPeerID: senderPeerID, senderPeer: senderPeer,
mentions: mentions mentions: mentions
) )
} }
+75
View File
@@ -0,0 +1,75 @@
//
// Peer.swift
// BitLogger
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Foundation
struct Peer: Equatable, Hashable {
let id: String
}
extension Peer {
var data: Data? {
id.data(using: .utf8)
}
var isNostr: Bool {
id.hasPrefix("nostr")
}
var isNostrColon: Bool {
id.hasPrefix("nostr:")
}
}
// MARK: - ExpressibleByStringLiteral
extension Peer: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(str: value)
}
}
// MARK: - ExpressibleByStringInterpolation
extension Peer: ExpressibleByStringInterpolation {
init(extendedGraphemeClusterLiteral value: String) {
self.init(str: value)
}
}
// MARK: - Codable
extension Peer: Codable {
init(from decoder: any Decoder) throws {
id = try decoder.singleValueContainer().decode(String.self)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
// MARK: - Convenience Inits
extension Peer {
init(str: String) {
id = str.lowercased()
}
init(str: String.SubSequence) {
self.init(str: String(str))
}
init?(data: Data) {
guard let str = String(data: data, encoding: .utf8) else {
return nil
}
self.init(str: str)
}
}
+4 -4
View File
@@ -73,7 +73,7 @@ final class PrivateChatManager: ObservableObject {
originalSender: nil, originalSender: nil,
isPrivate: true, isPrivate: true,
recipientNickname: peerNickname, recipientNickname: peerNickname,
senderPeerID: meshService.myPeerID, senderPeer: Peer(str: meshService.myPeerID),
mentions: nil, mentions: nil,
deliveryStatus: .sending deliveryStatus: .sending
) )
@@ -94,7 +94,7 @@ final class PrivateChatManager: ObservableObject {
/// Handle incoming private message /// Handle incoming private message
func handleIncomingMessage(_ message: BitchatMessage) { func handleIncomingMessage(_ message: BitchatMessage) {
guard let senderPeerID = message.senderPeerID else { return } guard let senderPeerID = message.senderPeer?.id else { return }
// Initialize chat if needed // Initialize chat if needed
if privateChats[senderPeerID] == nil { if privateChats[senderPeerID] == nil {
@@ -161,7 +161,7 @@ final class PrivateChatManager: ObservableObject {
// Send read receipts for unread messages that haven't been sent yet // Send read receipts for unread messages that haven't been sent yet
if let messages = privateChats[peerID] { if let messages = privateChats[peerID] {
for message in messages { for message in messages {
if message.senderPeerID == peerID && !message.isRelay && !sentReadReceipts.contains(message.id) { if message.senderPeer?.id == peerID && !message.isRelay && !sentReadReceipts.contains(message.id) {
sendReadReceipt(for: message) sendReadReceipt(for: message)
} }
} }
@@ -214,7 +214,7 @@ final class PrivateChatManager: ObservableObject {
private func sendReadReceipt(for message: BitchatMessage) { private func sendReadReceipt(for message: BitchatMessage) {
guard !sentReadReceipts.contains(message.id), guard !sentReadReceipts.contains(message.id),
let senderPeerID = message.senderPeerID else { let senderPeerID = message.senderPeer?.id else {
return return
} }
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -459,7 +459,7 @@ struct ContentView: View {
if let name = viewModel.meshService.peerNickname(peerID: peerID) { if let name = viewModel.meshService.peerNickname(peerID: peerID) {
selectedMessageSender = name selectedMessageSender = name
} else { } else {
selectedMessageSender = viewModel.messages.last(where: { $0.senderPeerID == peerID && $0.sender != "system" })?.sender selectedMessageSender = viewModel.messages.last(where: { $0.senderPeer?.id == peerID && $0.sender != "system" })?.sender
} }
} }
if viewModel.isSelfSender(peerID: selectedMessageSenderID, displayName: selectedMessageSender) { if viewModel.isSelfSender(peerID: selectedMessageSenderID, displayName: selectedMessageSender) {
+3 -3
View File
@@ -134,7 +134,7 @@ final class BLEServiceTests: XCTestCase {
originalSender: nil, originalSender: nil,
isPrivate: false, isPrivate: false,
recipientNickname: nil, recipientNickname: nil,
senderPeerID: "REMOTE123", senderPeer: "REMOTE123",
mentions: nil mentions: nil
) )
@@ -161,7 +161,7 @@ final class BLEServiceTests: XCTestCase {
originalSender: nil, originalSender: nil,
isPrivate: false, isPrivate: false,
recipientNickname: nil, recipientNickname: nil,
senderPeerID: "PACKET123", senderPeer: "PACKET123",
mentions: nil mentions: nil
) )
@@ -243,7 +243,7 @@ final class BLEServiceTests: XCTestCase {
originalSender: nil, originalSender: nil,
isPrivate: false, isPrivate: false,
recipientNickname: nil, recipientNickname: nil,
senderPeerID: "TEST123", senderPeer: "TEST123",
mentions: nil mentions: nil
) )
@@ -111,7 +111,7 @@ final class PublicChatE2ETests: XCTestCase {
originalSender: message.sender, originalSender: message.sender,
isPrivate: message.isPrivate, isPrivate: message.isPrivate,
recipientNickname: message.recipientNickname, recipientNickname: message.recipientNickname,
senderPeerID: message.senderPeerID, senderPeerID: message.senderPeer?.id,
mentions: message.mentions mentions: message.mentions
) )
@@ -439,7 +439,7 @@ final class PublicChatE2ETests: XCTestCase {
if let message = BitchatMessage(packet.payload) { if let message = BitchatMessage(packet.payload) {
// Don't relay own messages // Don't relay own messages
guard message.senderPeerID != node.peerID else { return } guard message.senderPeer?.id != node.peerID else { return }
// Create relay message // Create relay message
let relayMessage = BitchatMessage( let relayMessage = BitchatMessage(
@@ -451,7 +451,7 @@ final class PublicChatE2ETests: XCTestCase {
originalSender: message.isRelay ? message.originalSender : message.sender, originalSender: message.isRelay ? message.originalSender : message.sender,
isPrivate: message.isPrivate, isPrivate: message.isPrivate,
recipientNickname: message.recipientNickname, recipientNickname: message.recipientNickname,
senderPeerID: message.senderPeerID, senderPeerID: message.senderPeer?.id,
mentions: message.mentions mentions: message.mentions
) )
@@ -627,7 +627,7 @@ final class IntegrationTests: XCTestCase {
guard packet.ttl > 1 else { return } guard packet.ttl > 1 else { return }
if let message = BitchatMessage(packet.payload) { if let message = BitchatMessage(packet.payload) {
guard message.senderPeerID != node.peerID else { return } guard message.senderPeer?.id != node.peerID else { return }
let relayMessage = BitchatMessage( let relayMessage = BitchatMessage(
id: message.id, id: message.id,
@@ -638,7 +638,7 @@ final class IntegrationTests: XCTestCase {
originalSender: message.isRelay ? message.originalSender : message.sender, originalSender: message.isRelay ? message.originalSender : message.sender,
isPrivate: message.isPrivate, isPrivate: message.isPrivate,
recipientNickname: message.recipientNickname, recipientNickname: message.recipientNickname,
senderPeerID: message.senderPeerID, senderPeerID: message.senderPeer?.id,
mentions: message.mentions mentions: message.mentions
) )
+1 -1
View File
@@ -331,7 +331,7 @@ final class MockBLEService: NSObject {
let nextTTL = packet.ttl > 0 ? packet.ttl - 1 : 0 let nextTTL = packet.ttl > 0 ? packet.ttl - 1 : 0
for neighbor in neighbors() { for neighbor in neighbors() {
// Avoid immediate echo loopback to sender if known // Avoid immediate echo loopback to sender if known
if let sender = message.senderPeerID, sender == neighbor.peerID { continue } if let sender = message.senderPeer?.id, sender == neighbor.peerID { continue }
var relay = packet var relay = packet
relay.ttl = nextTTL relay.ttl = nextTTL
neighbor.simulateIncomingPacket(relay) neighbor.simulateIncomingPacket(relay)
@@ -204,7 +204,7 @@ final class BinaryProtocolTests: XCTestCase {
XCTAssertEqual(decodedMessage.content, message.content) XCTAssertEqual(decodedMessage.content, message.content)
XCTAssertEqual(decodedMessage.sender, message.sender) XCTAssertEqual(decodedMessage.sender, message.sender)
XCTAssertEqual(decodedMessage.senderPeerID, message.senderPeerID) XCTAssertEqual(decodedMessage.senderPeerID, message.senderPeer?.id)
XCTAssertEqual(decodedMessage.isPrivate, message.isPrivate) XCTAssertEqual(decodedMessage.isPrivate, message.isPrivate)
// Timestamp should be close (within 1 second due to conversion) // Timestamp should be close (within 1 second due to conversion)
+1 -1
View File
@@ -44,7 +44,7 @@ final class TestHelpers {
originalSender: nil, originalSender: nil,
isPrivate: isPrivate, isPrivate: isPrivate,
recipientNickname: recipientNickname, recipientNickname: recipientNickname,
senderPeerID: senderPeerID, senderPeer: senderPeerID,
mentions: mentions mentions: mentions
) )
} }