mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:45:19 +00:00
Introduce Peer for safer handling of peerID
This commit is contained in:
@@ -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?,
|
||||||
|
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,12 +64,43 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BitchatMessage {
|
||||||
|
// Temp solution to minimize the code-change
|
||||||
|
convenience 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.init(
|
||||||
|
id: id,
|
||||||
|
sender: sender,
|
||||||
|
content: content,
|
||||||
|
timestamp: timestamp,
|
||||||
|
isRelay: isRelay,
|
||||||
|
originalSender: originalSender,
|
||||||
|
isPrivate: isPrivate,
|
||||||
|
recipientNickname: recipientNickname,
|
||||||
|
senderPeer: senderPeerID != nil ? Peer(str: senderPeerID!) : nil,
|
||||||
|
mentions: mentions,
|
||||||
|
deliveryStatus: deliveryStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Equatable Conformance
|
// MARK: - Equatable Conformance
|
||||||
|
|
||||||
extension BitchatMessage: Equatable {
|
extension BitchatMessage: Equatable {
|
||||||
@@ -69,7 +113,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 +145,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 +195,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 +308,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 +346,7 @@ extension BitchatMessage {
|
|||||||
originalSender: originalSender,
|
originalSender: originalSender,
|
||||||
isPrivate: isPrivate,
|
isPrivate: isPrivate,
|
||||||
recipientNickname: recipientNickname,
|
recipientNickname: recipientNickname,
|
||||||
senderPeerID: senderPeerID,
|
senderPeer: senderPeer,
|
||||||
mentions: mentions
|
mentions: mentions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func normalizedSenderKey(for message: BitchatMessage) -> String {
|
private func normalizedSenderKey(for message: BitchatMessage) -> String {
|
||||||
if let spid = message.senderPeerID {
|
if let spid = message.senderPeer?.id {
|
||||||
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
||||||
let bare: String = {
|
let bare: String = {
|
||||||
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
||||||
@@ -1231,7 +1231,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// If no nickname in status, try to get from private chat messages
|
// If no nickname in status, try to get from private chat messages
|
||||||
if nickname == nil, let messages = privateChats[peerID], !messages.isEmpty {
|
if nickname == nil, let messages = privateChats[peerID], !messages.isEmpty {
|
||||||
// Get the nickname from the first message where this peer was the sender
|
// Get the nickname from the first message where this peer was the sender
|
||||||
nickname = messages.first { $0.senderPeerID == peerID }?.sender
|
nickname = messages.first { $0.senderPeer?.id == peerID }?.sender
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalNickname = nickname ?? "Unknown"
|
let finalNickname = nickname ?? "Unknown"
|
||||||
@@ -2010,7 +2010,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if let gh = currentGeohash {
|
if let gh = currentGeohash {
|
||||||
if var arr = geoTimelines[gh] {
|
if var arr = geoTimelines[gh] {
|
||||||
arr.removeAll { msg in
|
arr.removeAll { msg in
|
||||||
if let spid = msg.senderPeerID, spid.hasPrefix("nostr") {
|
if let spid = msg.senderPeer?.id, msg.senderPeer?.isNostr == true {
|
||||||
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -2021,7 +2021,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
switch activeChannel {
|
switch activeChannel {
|
||||||
case .location:
|
case .location:
|
||||||
messages.removeAll { msg in
|
messages.removeAll { msg in
|
||||||
if let spid = msg.senderPeerID, spid.hasPrefix("nostr") {
|
if let spid = msg.senderPeer?.id, msg.senderPeer?.isNostr == true {
|
||||||
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex }
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -2501,7 +2501,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
originalSender: message.originalSender,
|
originalSender: message.originalSender,
|
||||||
isPrivate: message.isPrivate,
|
isPrivate: message.isPrivate,
|
||||||
recipientNickname: message.recipientNickname,
|
recipientNickname: message.recipientNickname,
|
||||||
senderPeerID: message.senderPeerID == meshService.myPeerID ? meshService.myPeerID : peerID, // Update peer ID if it's from them
|
senderPeerID: message.senderPeer?.id == meshService.myPeerID ? meshService.myPeerID : peerID, // Update peer ID if it's from them
|
||||||
mentions: message.mentions,
|
mentions: message.mentions,
|
||||||
deliveryStatus: message.deliveryStatus
|
deliveryStatus: message.deliveryStatus
|
||||||
)
|
)
|
||||||
@@ -2512,7 +2512,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// 1. Not a message we sent
|
// 1. Not a message we sent
|
||||||
// 2. Message is recent (< 60s old)
|
// 2. Message is recent (< 60s old)
|
||||||
// Never mark old messages as unread during consolidation
|
// Never mark old messages as unread during consolidation
|
||||||
if message.senderPeerID != meshService.myPeerID {
|
if message.senderPeer?.id != meshService.myPeerID {
|
||||||
let messageAge = Date().timeIntervalSince(message.timestamp)
|
let messageAge = Date().timeIntervalSince(message.timestamp)
|
||||||
if messageAge < 60 && !sentReadReceipts.contains(message.id) {
|
if messageAge < 60 && !sentReadReceipts.contains(message.id) {
|
||||||
hasActualUnreadMessages = true
|
hasActualUnreadMessages = true
|
||||||
@@ -2671,7 +2671,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Store the Nostr pubkey if provided (for messages from unknown senders)
|
// Store the Nostr pubkey if provided (for messages from unknown senders)
|
||||||
if let nostrPubkey = notification.userInfo?["nostrPubkey"] as? String,
|
if let nostrPubkey = notification.userInfo?["nostrPubkey"] as? String,
|
||||||
let senderPeerID = message.senderPeerID {
|
let senderPeerID = message.senderPeer?.id {
|
||||||
// Store mapping for read receipts
|
// Store mapping for read receipts
|
||||||
nostrKeyMapping[senderPeerID] = nostrPubkey
|
nostrKeyMapping[senderPeerID] = nostrPubkey
|
||||||
}
|
}
|
||||||
@@ -3001,7 +3001,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
case .location(let ch) = LocationChannelManager.shared.selectedChannel,
|
case .location(let ch) = LocationChannelManager.shared.selectedChannel,
|
||||||
let id = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) {
|
let id = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) {
|
||||||
let messages = privateChats[peerID] ?? []
|
let messages = privateChats[peerID] ?? []
|
||||||
for message in messages where message.senderPeerID == peerID && !message.isRelay {
|
for message in messages where message.senderPeer?.id == peerID && !message.isRelay {
|
||||||
if !sentReadReceipts.contains(message.id) {
|
if !sentReadReceipts.contains(message.id) {
|
||||||
SecureLogger.debug("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…", category: .session)
|
SecureLogger.debug("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…", category: .session)
|
||||||
let nostrTransport = NostrTransport(keychain: keychain)
|
let nostrTransport = NostrTransport(keychain: keychain)
|
||||||
@@ -3043,7 +3043,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
for message in messagesToAck {
|
for message in messagesToAck {
|
||||||
// Only send read ACKs for messages from the peer (not our own)
|
// Only send read ACKs for messages from the peer (not our own)
|
||||||
// Check both the ephemeral peer ID and stable Noise key as sender
|
// Check both the ephemeral peer ID and stable Noise key as sender
|
||||||
if (message.senderPeerID == peerID || message.senderPeerID == noiseKeyHex) && !message.isRelay {
|
if (message.senderPeer?.id == peerID || message.senderPeer?.id == noiseKeyHex) && !message.isRelay {
|
||||||
// Skip if we already sent an ACK for this message
|
// Skip if we already sent an ACK for this message
|
||||||
if !sentReadReceipts.contains(message.id) {
|
if !sentReadReceipts.contains(message.id) {
|
||||||
// Use stable Noise key hex if available; else fall back to peerID
|
// Use stable Noise key hex if available; else fall back to peerID
|
||||||
@@ -3290,7 +3290,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
func formatMessageAsText(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString {
|
func formatMessageAsText(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString {
|
||||||
// Determine if this message was sent by self (mesh, geo, or DM)
|
// Determine if this message was sent by self (mesh, geo, or DM)
|
||||||
let isSelf: Bool = {
|
let isSelf: Bool = {
|
||||||
if let spid = message.senderPeerID {
|
if let spid = message.senderPeer?.id {
|
||||||
// In geohash channels, compare against our per-geohash nostr short ID
|
// In geohash channels, compare against our per-geohash nostr short ID
|
||||||
if case .location(let ch) = activeChannel, spid.hasPrefix("nostr:") {
|
if case .location(let ch) = activeChannel, spid.hasPrefix("nostr:") {
|
||||||
if let myGeo = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) {
|
if let myGeo = try? NostrIdentityBridge.deriveIdentity(forGeohash: ch.geohash) {
|
||||||
@@ -3325,7 +3325,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
let fontWeight: Font.Weight = isSelf ? .bold : .medium
|
let fontWeight: Font.Weight = isSelf ? .bold : .medium
|
||||||
senderStyle.font = .system(size: 14, weight: fontWeight, design: .monospaced)
|
senderStyle.font = .system(size: 14, weight: fontWeight, design: .monospaced)
|
||||||
// Make sender clickable: encode senderPeerID into a custom URL
|
// 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.senderPeer?.id, let url = URL(string: "bitchat://user/\(spid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spid)") {
|
||||||
senderStyle.link = url
|
senderStyle.link = url
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3855,7 +3855,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func peerColor(for message: BitchatMessage, isDark: Bool) -> Color {
|
private func peerColor(for message: BitchatMessage, isDark: Bool) -> Color {
|
||||||
if let spid = message.senderPeerID {
|
if let spid = message.senderPeer?.id {
|
||||||
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
if spid.hasPrefix("nostr:") || spid.hasPrefix("nostr_") {
|
||||||
let bare: String = {
|
let bare: String = {
|
||||||
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
if spid.hasPrefix("nostr:") { return String(spid.dropFirst(6)) }
|
||||||
@@ -4659,7 +4659,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
originalSender: msg.originalSender,
|
originalSender: msg.originalSender,
|
||||||
isPrivate: msg.isPrivate,
|
isPrivate: msg.isPrivate,
|
||||||
recipientNickname: msg.recipientNickname,
|
recipientNickname: msg.recipientNickname,
|
||||||
senderPeerID: (msg.senderPeerID == meshService.myPeerID) ? meshService.myPeerID : stableKeyHex,
|
senderPeerID: (msg.senderPeer?.id == meshService.myPeerID) ? meshService.myPeerID : stableKeyHex,
|
||||||
mentions: msg.mentions,
|
mentions: msg.mentions,
|
||||||
deliveryStatus: msg.deliveryStatus
|
deliveryStatus: msg.deliveryStatus
|
||||||
)
|
)
|
||||||
@@ -4687,7 +4687,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if let messages = privateChats[peerID] {
|
if let messages = privateChats[peerID] {
|
||||||
for message in messages {
|
for message in messages {
|
||||||
// Remove read receipts for messages FROM this peer (not TO this peer)
|
// Remove read receipts for messages FROM this peer (not TO this peer)
|
||||||
if message.senderPeerID == peerID {
|
if message.senderPeer?.id == peerID {
|
||||||
sentReadReceipts.remove(message.id)
|
sentReadReceipts.remove(message.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5548,7 +5548,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if senderNickname == nil {
|
if senderNickname == nil {
|
||||||
for (_, messages) in privateChats {
|
for (_, messages) in privateChats {
|
||||||
if let previousMessage = messages.first(where: {
|
if let previousMessage = messages.first(where: {
|
||||||
$0.senderPeerID == tempPeerID
|
$0.senderPeer?.id == tempPeerID
|
||||||
}) {
|
}) {
|
||||||
finalSenderNickname = previousMessage.sender
|
finalSenderNickname = previousMessage.sender
|
||||||
break
|
break
|
||||||
@@ -5691,7 +5691,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
/// Check if a message should be blocked based on sender
|
/// Check if a message should be blocked based on sender
|
||||||
@MainActor
|
@MainActor
|
||||||
private func isMessageBlocked(_ message: BitchatMessage) -> Bool {
|
private func isMessageBlocked(_ message: BitchatMessage) -> Bool {
|
||||||
if let peerID = message.senderPeerID ?? getPeerIDForNickname(message.sender) {
|
if let peerID = message.senderPeer?.id ?? getPeerIDForNickname(message.sender) {
|
||||||
// Check mesh/known peers first
|
// Check mesh/known peers first
|
||||||
if isPeerBlocked(peerID) { return true }
|
if isPeerBlocked(peerID) { return true }
|
||||||
// Check geohash (Nostr) blocks using mapping to full pubkey
|
// Check geohash (Nostr) blocks using mapping to full pubkey
|
||||||
@@ -5731,7 +5731,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
originalSender: message.originalSender,
|
originalSender: message.originalSender,
|
||||||
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,
|
||||||
deliveryStatus: message.deliveryStatus
|
deliveryStatus: message.deliveryStatus
|
||||||
)
|
)
|
||||||
@@ -5840,7 +5840,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private func handlePrivateMessage(_ message: BitchatMessage) {
|
private func handlePrivateMessage(_ message: BitchatMessage) {
|
||||||
SecureLogger.debug("📥 handlePrivateMessage called for message from \(message.sender)", category: .session)
|
SecureLogger.debug("📥 handlePrivateMessage called for message from \(message.sender)", category: .session)
|
||||||
let senderPeerID = message.senderPeerID ?? getPeerIDForNickname(message.sender)
|
let senderPeerID = message.senderPeer?.id ?? getPeerIDForNickname(message.sender)
|
||||||
|
|
||||||
guard let peerID = senderPeerID else {
|
guard let peerID = senderPeerID else {
|
||||||
SecureLogger.warning("⚠️ Could not get peer ID for sender \(message.sender)", category: .session)
|
SecureLogger.warning("⚠️ Could not get peer ID for sender \(message.sender)", category: .session)
|
||||||
@@ -5951,7 +5951,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
readerNickname: nickname
|
readerNickname: nickname
|
||||||
)
|
)
|
||||||
|
|
||||||
let recipientID = message.senderPeerID ?? peerID
|
let recipientID = message.senderPeer?.id ?? peerID
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
var originalTransport: String? = nil
|
var originalTransport: String? = nil
|
||||||
@@ -5983,7 +5983,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if isMessageBlocked(finalMessage) { return }
|
if isMessageBlocked(finalMessage) { return }
|
||||||
|
|
||||||
// Classify origin: geochat if senderPeerID starts with 'nostr:', else mesh (or system)
|
// Classify origin: geochat if senderPeerID starts with 'nostr:', else mesh (or system)
|
||||||
let isGeo = finalMessage.senderPeerID?.hasPrefix("nostr:") == true
|
let isGeo = finalMessage.senderPeer?.isNostrColon == true
|
||||||
|
|
||||||
// Apply per-sender and per-content rate limits (drop if exceeded)
|
// Apply per-sender and per-content rate limits (drop if exceeded)
|
||||||
if finalMessage.sender != "system" {
|
if finalMessage.sender != "system" {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user