mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 08:45:19 +00:00
PeerID 4/n: BitchatMessage.senderPeerID + String equality (#739)
This commit is contained in:
@@ -21,7 +21,7 @@ final class BitchatMessage: Codable {
|
||||
let originalSender: String?
|
||||
let isPrivate: Bool
|
||||
let recipientNickname: String?
|
||||
let senderPeerID: String?
|
||||
let senderPeerID: PeerID?
|
||||
let mentions: [String]? // Array of mentioned nicknames
|
||||
var deliveryStatus: DeliveryStatus? // Delivery tracking
|
||||
|
||||
@@ -51,7 +51,7 @@ final class BitchatMessage: Codable {
|
||||
self.originalSender = originalSender
|
||||
self.isPrivate = isPrivate
|
||||
self.recipientNickname = recipientNickname
|
||||
self.senderPeerID = senderPeerID
|
||||
self.senderPeerID = PeerID(str: senderPeerID)
|
||||
self.mentions = mentions
|
||||
self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil)
|
||||
}
|
||||
@@ -151,7 +151,7 @@ extension BitchatMessage {
|
||||
data.append(recipData.prefix(255))
|
||||
}
|
||||
|
||||
if let senderPeerID = senderPeerID, let peerData = senderPeerID.data(using: .utf8) {
|
||||
if let peerData = senderPeerID?.id.data(using: .utf8) {
|
||||
data.append(UInt8(min(peerData.count, 255)))
|
||||
data.append(peerData.prefix(255))
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ extension PeerID {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience init to handle `Optional<String>`
|
||||
init?(str: (any StringProtocol)?) {
|
||||
guard let str else { return nil }
|
||||
self.init(str: str)
|
||||
}
|
||||
|
||||
/// Convenience init to create PeerID by converting Data to String
|
||||
init?(data: Data) {
|
||||
guard let str = String(data: data, encoding: .utf8) else { return nil }
|
||||
@@ -175,3 +181,28 @@ extension PeerID: Comparable {
|
||||
lhs.id < rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - String Interop Helpers
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
extension PeerID: CustomStringConvertible {
|
||||
/// So it returns the actual `id` like before even inside another String
|
||||
var description: String {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Custom Equatable w/ String & Optionality
|
||||
|
||||
// PeerID <> String
|
||||
extension Optional where Wrapped == PeerID {
|
||||
static func ==(lhs: Optional<Wrapped>, rhs: Optional<String>) -> Bool { lhs?.id == rhs }
|
||||
static func !=(lhs: Optional<Wrapped>, rhs: Optional<String>) -> Bool { lhs?.id != rhs }
|
||||
}
|
||||
|
||||
// String <> PeerID
|
||||
extension Optional where Wrapped == String {
|
||||
static func ==(lhs: Optional<Wrapped>, rhs: Optional<PeerID>) -> Bool { lhs == rhs?.id }
|
||||
static func !=(lhs: Optional<Wrapped>, rhs: Optional<PeerID>) -> Bool { lhs != rhs?.id }
|
||||
}
|
||||
|
||||
@@ -111,13 +111,13 @@ final class PrivateChatManager: ObservableObject {
|
||||
|
||||
// Route via MessageRouter to avoid handshakeRequired spam when session isn't established
|
||||
if let router = messageRouter {
|
||||
SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.prefix(8))… via router", category: .session)
|
||||
SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.id.prefix(8))… via router", category: .session)
|
||||
Task { @MainActor in
|
||||
router.sendReadReceipt(receipt, to: senderPeerID)
|
||||
router.sendReadReceipt(receipt, to: senderPeerID.id)
|
||||
}
|
||||
} else {
|
||||
// Fallback: preserve previous behavior
|
||||
meshService?.sendReadReceipt(receipt, to: senderPeerID)
|
||||
meshService?.sendReadReceipt(receipt, to: senderPeerID.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
|
||||
@MainActor
|
||||
private func normalizedSenderKey(for message: BitchatMessage) -> String {
|
||||
if let spid = message.senderPeerID {
|
||||
if let spid = message.senderPeerID?.id {
|
||||
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
||||
let bare: String = {
|
||||
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
||||
@@ -1967,7 +1967,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
if let gh = currentGeohash {
|
||||
if var arr = geoTimelines[gh] {
|
||||
arr.removeAll { msg in
|
||||
if let spid = msg.senderPeerID, spid.hasPrefix("nostr") {
|
||||
if let spid = msg.senderPeerID?.id, spid.hasPrefix("nostr") {
|
||||
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
||||
}
|
||||
return false
|
||||
@@ -1978,7 +1978,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
switch activeChannel {
|
||||
case .location:
|
||||
messages.removeAll { msg in
|
||||
if let spid = msg.senderPeerID, spid.hasPrefix("nostr") {
|
||||
if let spid = msg.senderPeerID?.id , spid.hasPrefix("nostr") {
|
||||
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
||||
}
|
||||
return false
|
||||
@@ -2676,7 +2676,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
|
||||
// Store the Nostr pubkey if provided (for messages from unknown senders)
|
||||
if let nostrPubkey = notification.userInfo?["nostrPubkey"] as? String,
|
||||
let senderPeerID = message.senderPeerID {
|
||||
let senderPeerID = message.senderPeerID?.id {
|
||||
// Store mapping for read receipts
|
||||
nostrKeyMapping[senderPeerID] = nostrPubkey
|
||||
}
|
||||
@@ -3296,7 +3296,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
func formatMessageAsText(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString {
|
||||
// Determine if this message was sent by self (mesh, geo, or DM)
|
||||
let isSelf: Bool = {
|
||||
if let spid = message.senderPeerID {
|
||||
if let spid = message.senderPeerID?.id {
|
||||
// In geohash channels, compare against our per-geohash nostr short ID
|
||||
if case .location(let ch) = activeChannel, spid.hasPrefix("nostr:") {
|
||||
if let myGeo = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) {
|
||||
@@ -3331,7 +3331,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let fontWeight: Font.Weight = isSelf ? .bold : .medium
|
||||
senderStyle.font = .bitchatSystem(size: 14, weight: fontWeight, design: .monospaced)
|
||||
// Make sender clickable: encode senderPeerID into a custom URL
|
||||
if let spid = message.senderPeerID, let url = URL(string: "bitchat://user/\(spid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spid)") {
|
||||
if let spid = message.senderPeerID?.id, let url = URL(string: "bitchat://user/\(spid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spid)") {
|
||||
senderStyle.link = url
|
||||
}
|
||||
|
||||
@@ -3862,7 +3862,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
|
||||
@MainActor
|
||||
private func peerColor(for message: BitchatMessage, isDark: Bool) -> Color {
|
||||
if let spid = message.senderPeerID {
|
||||
if let spid = message.senderPeerID?.id {
|
||||
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
||||
let bare: String = {
|
||||
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
||||
@@ -5654,7 +5654,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
/// Check if a message should be blocked based on sender
|
||||
@MainActor
|
||||
private func isMessageBlocked(_ message: BitchatMessage) -> Bool {
|
||||
if let peerID = message.senderPeerID ?? getPeerIDForNickname(message.sender) {
|
||||
if let peerID = message.senderPeerID?.id ?? getPeerIDForNickname(message.sender) {
|
||||
// Check mesh/known peers first
|
||||
if isPeerBlocked(peerID) { return true }
|
||||
// Check geohash (Nostr) blocks using mapping to full pubkey
|
||||
@@ -5694,7 +5694,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
originalSender: message.originalSender,
|
||||
isPrivate: message.isPrivate,
|
||||
recipientNickname: message.recipientNickname,
|
||||
senderPeerID: message.senderPeerID,
|
||||
senderPeerID: message.senderPeerID?.id,
|
||||
mentions: message.mentions,
|
||||
deliveryStatus: message.deliveryStatus
|
||||
)
|
||||
@@ -5802,7 +5802,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
@MainActor
|
||||
private func handlePrivateMessage(_ message: BitchatMessage) {
|
||||
SecureLogger.debug("📥 handlePrivateMessage called for message from \(message.sender)", category: .session)
|
||||
let senderPeerID = message.senderPeerID ?? getPeerIDForNickname(message.sender)
|
||||
let senderPeerID = message.senderPeerID?.id ?? getPeerIDForNickname(message.sender)
|
||||
|
||||
guard let peerID = senderPeerID else {
|
||||
SecureLogger.warning("⚠️ Could not get peer ID for sender \(message.sender)", category: .session)
|
||||
@@ -5912,7 +5912,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
readerNickname: nickname
|
||||
)
|
||||
|
||||
let recipientID = message.senderPeerID ?? peerID
|
||||
let recipientID = message.senderPeerID?.id ?? peerID
|
||||
|
||||
Task { @MainActor in
|
||||
var originalTransport: String? = nil
|
||||
@@ -5944,7 +5944,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
if isMessageBlocked(finalMessage) { return }
|
||||
|
||||
// Classify origin: geochat if senderPeerID starts with 'nostr:', else mesh (or system)
|
||||
let isGeo = finalMessage.senderPeerID?.hasPrefix("nostr:") == true
|
||||
let isGeo = finalMessage.senderPeerID?.isGeoChat == true
|
||||
|
||||
// Apply per-sender and per-content rate limits (drop if exceeded)
|
||||
if finalMessage.sender != "system" {
|
||||
|
||||
@@ -111,7 +111,7 @@ final class PublicChatE2ETests: XCTestCase {
|
||||
originalSender: message.sender,
|
||||
isPrivate: message.isPrivate,
|
||||
recipientNickname: message.recipientNickname,
|
||||
senderPeerID: message.senderPeerID,
|
||||
senderPeerID: message.senderPeerID?.id,
|
||||
mentions: message.mentions
|
||||
)
|
||||
|
||||
@@ -451,7 +451,7 @@ final class PublicChatE2ETests: XCTestCase {
|
||||
originalSender: message.isRelay ? message.originalSender : message.sender,
|
||||
isPrivate: message.isPrivate,
|
||||
recipientNickname: message.recipientNickname,
|
||||
senderPeerID: message.senderPeerID,
|
||||
senderPeerID: message.senderPeerID?.id,
|
||||
mentions: message.mentions
|
||||
)
|
||||
|
||||
|
||||
@@ -638,7 +638,7 @@ final class IntegrationTests: XCTestCase {
|
||||
originalSender: message.isRelay ? message.originalSender : message.sender,
|
||||
isPrivate: message.isPrivate,
|
||||
recipientNickname: message.recipientNickname,
|
||||
senderPeerID: message.senderPeerID,
|
||||
senderPeerID: message.senderPeerID?.id,
|
||||
mentions: message.mentions
|
||||
)
|
||||
|
||||
|
||||
@@ -290,6 +290,48 @@ final class PeerIDTests: XCTestCase {
|
||||
XCTAssertEqual(sorted, [p1, p2])
|
||||
}
|
||||
|
||||
func test_equality() {
|
||||
let string = "aaa"
|
||||
let peerID = PeerID(str: string)
|
||||
let badString = "bbb"
|
||||
|
||||
// PeerID == String
|
||||
XCTAssertTrue(peerID == string)
|
||||
XCTAssertTrue(peerID == Optional(string))
|
||||
XCTAssertTrue(Optional(peerID) == string)
|
||||
XCTAssertTrue(Optional(peerID) == Optional(string))
|
||||
|
||||
// PeerID != String
|
||||
XCTAssertTrue(peerID != badString)
|
||||
XCTAssertTrue(peerID != Optional(badString))
|
||||
XCTAssertTrue(Optional(peerID) != badString)
|
||||
XCTAssertTrue(Optional(peerID) != Optional(badString))
|
||||
|
||||
// String == PeerID
|
||||
XCTAssertTrue(string == peerID)
|
||||
XCTAssertTrue(Optional(string) == peerID)
|
||||
XCTAssertTrue(string == Optional(peerID))
|
||||
XCTAssertTrue(Optional(string) == Optional(peerID))
|
||||
|
||||
// String != PeerID
|
||||
XCTAssertTrue(badString != peerID)
|
||||
XCTAssertTrue(Optional(badString) != peerID)
|
||||
XCTAssertTrue(badString != Optional(peerID))
|
||||
XCTAssertTrue(Optional(badString) != Optional(peerID))
|
||||
|
||||
|
||||
// Make sure the regular PeerID <> PeerID is not broken
|
||||
XCTAssertTrue(peerID == PeerID(str: "aaa"))
|
||||
XCTAssertTrue(peerID == Optional(PeerID(str: "aaa")))
|
||||
XCTAssertTrue(PeerID(str: "aaa") == peerID)
|
||||
XCTAssertTrue(Optional(PeerID(str: "aaa")) == Optional(peerID))
|
||||
|
||||
XCTAssertTrue(peerID != PeerID(str: "bbb"))
|
||||
XCTAssertTrue(peerID != Optional(PeerID(str: "bbb")))
|
||||
XCTAssertTrue(PeerID(str: "bbb") != peerID)
|
||||
XCTAssertTrue(Optional(PeerID(str: "bbb")) != Optional(peerID))
|
||||
}
|
||||
|
||||
// MARK: - Computed properties
|
||||
|
||||
func test_isEmpty_true_and_false() {
|
||||
|
||||
Reference in New Issue
Block a user