Extract processNostrMessage into a function

This commit is contained in:
islam
2025-09-18 02:09:05 +01:00
parent 221819b591
commit 588cef72ce
+208 -134
View File
@@ -5049,8 +5049,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
return // Ignoring old message return // Ignoring old message
} }
// Processing Nostr message processNostrMessage(giftWrap)
}
@MainActor
private func processNostrMessage(_ giftWrap: NostrEvent) {
guard let currentIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return } guard let currentIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return }
do { do {
@@ -5101,141 +5104,15 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
switch noisePayload.type { switch noisePayload.type {
case .privateMessage: case .privateMessage:
guard let pm = PrivateMessagePacket.decode(from: noisePayload.data) else { return } handlePrivateMessage(
let messageId = pm.messageID noisePayload,
let messageContent = pm.content actualSenderNoiseKey: actualSenderNoiseKey,
senderNickname: senderNickname,
// Favorite/unfavorite notifications embedded as private messages targetPeerID: targetPeerID,
if messageContent.hasPrefix("[FAVORITED]") || messageContent.hasPrefix("[UNFAVORITED]") { messageTimestamp: messageTimestamp,
if let key = actualSenderNoiseKey { senderPubkey: senderPubkey
handleFavoriteNotificationFromMesh(messageContent, from: key.hexEncodedString(), senderNickname: senderNickname)
}
return
}
// Check for duplicate
var messageExistsLocally = false
if privateChats[targetPeerID]?.contains(where: { $0.id == messageId }) == true {
messageExistsLocally = true
}
if !messageExistsLocally {
for (_, messages) in privateChats {
if messages.contains(where: { $0.id == messageId }) { messageExistsLocally = true; break }
}
}
if messageExistsLocally { return }
let wasReadBefore = sentReadReceipts.contains(messageId)
// Is viewing?
var isViewingThisChat = false
if selectedPrivateChatPeer == targetPeerID {
isViewingThisChat = true
} else if let selectedPeer = selectedPrivateChatPeer,
let selectedPeerData = unifiedPeerService.getPeer(by: selectedPeer),
let key = actualSenderNoiseKey,
selectedPeerData.noisePublicKey == key {
isViewingThisChat = true
}
// Recency check
let isRecentMessage = Date().timeIntervalSince(messageTimestamp) < 30
let shouldMarkAsUnread = !wasReadBefore && !isViewingThisChat && (isRecentMessage || !isStartupPhase)
let message = BitchatMessage(
id: messageId,
sender: senderNickname,
content: messageContent,
timestamp: messageTimestamp,
isRelay: false,
originalSender: nil,
isPrivate: true,
recipientNickname: nickname,
senderPeerID: targetPeerID,
mentions: nil,
deliveryStatus: .delivered(to: nickname, at: Date())
) )
if privateChats[targetPeerID] == nil { privateChats[targetPeerID] = [] }
if let idx = privateChats[targetPeerID]?.firstIndex(where: { $0.id == messageId }) {
privateChats[targetPeerID]?[idx] = message
} else {
privateChats[targetPeerID]?.append(message)
}
// Sanitize to avoid duplicate IDs
privateChatManager.sanitizeChat(for: targetPeerID)
trimPrivateChatMessagesIfNeeded(for: targetPeerID)
// Mirror to ephemeral if applicable
if let key = actualSenderNoiseKey,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id,
ephemeralPeerID != targetPeerID {
if privateChats[ephemeralPeerID] == nil { privateChats[ephemeralPeerID] = [] }
if let idx = privateChats[ephemeralPeerID]?.firstIndex(where: { $0.id == messageId }) {
privateChats[ephemeralPeerID]?[idx] = message
} else {
privateChats[ephemeralPeerID]?.append(message)
trimPrivateChatMessagesIfNeeded(for: ephemeralPeerID)
}
privateChatManager.sanitizeChat(for: ephemeralPeerID)
}
// Send delivery ack via Nostr embedded
if !wasReadBefore {
if let key = actualSenderNoiseKey {
SecureLogger.debug("Sending DELIVERED ack for \(messageId.prefix(8))… via router", category: .session)
messageRouter.sendDeliveryAck(messageId, to: key.hexEncodedString())
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
// Fallback: no Noise mapping yet send directly to sender's Nostr pubkey
let nt = NostrTransport(keychain: keychain)
nt.senderPeerID = meshService.myPeerID
nt.sendDeliveryAckGeohash(for: messageId, toRecipientHex: senderPubkey, from: id)
SecureLogger.debug("Sent DELIVERED ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))", category: .session)
}
}
if wasReadBefore {
// do nothing
} else if isViewingThisChat {
unreadPrivateMessages.remove(targetPeerID)
if let key = actualSenderNoiseKey,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id {
unreadPrivateMessages.remove(ephemeralPeerID)
}
if !sentReadReceipts.contains(messageId) {
if let key = actualSenderNoiseKey {
let receipt = ReadReceipt(originalMessageID: messageId, readerID: meshService.myPeerID, readerNickname: nickname)
SecureLogger.debug("Viewing chat; sending READ ack for \(messageId.prefix(8))… via router", category: .session)
messageRouter.sendReadReceipt(receipt, to: key.hexEncodedString())
sentReadReceipts.insert(messageId)
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
let nt = NostrTransport(keychain: keychain)
nt.senderPeerID = meshService.myPeerID
nt.sendReadReceiptGeohash(messageId, toRecipientHex: senderPubkey, from: id)
sentReadReceipts.insert(messageId)
SecureLogger.debug("Viewing chat; sent READ ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))", category: .session)
}
}
} else {
if shouldMarkAsUnread {
unreadPrivateMessages.insert(targetPeerID)
if let key = actualSenderNoiseKey,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id,
ephemeralPeerID != targetPeerID {
unreadPrivateMessages.insert(ephemeralPeerID)
}
if isRecentMessage {
NotificationService.shared.sendPrivateMessageNotification(
from: senderNickname,
message: messageContent,
peerID: targetPeerID
)
}
}
}
objectWillChange.send()
case .delivered: case .delivered:
guard let messageID = String(data: noisePayload.data, encoding: .utf8) else { return } guard let messageID = String(data: noisePayload.data, encoding: .utf8) else { return }
let peerName = senderNickname let peerName = senderNickname
@@ -5262,6 +5139,203 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
} }
} }
@MainActor
private func handlePrivateMessage(
_ payload: NoisePayload,
actualSenderNoiseKey: Data?,
senderNickname: String,
targetPeerID: String,
messageTimestamp: Date,
senderPubkey: String
) {
guard let pm = PrivateMessagePacket.decode(from: payload.data) else { return }
let messageId = pm.messageID
let messageContent = pm.content
// Favorite/unfavorite notifications embedded as private messages
if messageContent.hasPrefix("[FAVORITED]") || messageContent.hasPrefix("[UNFAVORITED]") {
if let key = actualSenderNoiseKey {
handleFavoriteNotificationFromMesh(messageContent, from: key.hexEncodedString(), senderNickname: senderNickname)
}
return
}
if isDuplicateMessage(messageId, targetPeerID: targetPeerID) {
return
}
let wasReadBefore = sentReadReceipts.contains(messageId)
// Is viewing?
var isViewingThisChat = false
if selectedPrivateChatPeer == targetPeerID {
isViewingThisChat = true
} else if let selectedPeer = selectedPrivateChatPeer,
let selectedPeerData = unifiedPeerService.getPeer(by: selectedPeer),
let key = actualSenderNoiseKey,
selectedPeerData.noisePublicKey == key {
isViewingThisChat = true
}
// Recency check
let isRecentMessage = Date().timeIntervalSince(messageTimestamp) < 30
let shouldMarkAsUnread = !wasReadBefore && !isViewingThisChat && (isRecentMessage || !isStartupPhase)
let message = BitchatMessage(
id: messageId,
sender: senderNickname,
content: messageContent,
timestamp: messageTimestamp,
isRelay: false,
isPrivate: true,
recipientNickname: nickname,
senderPeerID: targetPeerID,
deliveryStatus: .delivered(to: nickname, at: Date())
)
addMessageToPrivateChatsIfNeeded(message, targetPeerID: targetPeerID)
mirrorToEphemeralIfNeeded(message, targetPeerID: targetPeerID, key: actualSenderNoiseKey)
sendDeliveryAckViaNostrEmbedded(
message,
wasReadBefore: wasReadBefore,
senderPubkey: senderPubkey,
key: actualSenderNoiseKey
)
if wasReadBefore {
// do nothing
} else if isViewingThisChat {
handleViewingThisChat(
message,
targetPeerID: targetPeerID,
key: actualSenderNoiseKey,
senderPubkey: senderPubkey
)
} else {
markAsUnreadIfNeeded(
shouldMarkAsUnread: shouldMarkAsUnread,
targetPeerID: targetPeerID,
key: actualSenderNoiseKey,
isRecentMessage: isRecentMessage,
senderNickname: senderNickname,
messageContent: messageContent
)
}
objectWillChange.send()
}
private func isDuplicateMessage(_ messageId: String, targetPeerID: String) -> Bool {
if privateChats[targetPeerID]?.contains(where: { $0.id == messageId }) == true {
return true
}
for (_, messages) in privateChats where messages.contains(where: { $0.id == messageId }) {
return true
}
return false
}
private func addMessageToPrivateChatsIfNeeded(_ message: BitchatMessage, targetPeerID: String) {
if privateChats[targetPeerID] == nil {
privateChats[targetPeerID] = []
}
if let idx = privateChats[targetPeerID]?.firstIndex(where: { $0.id == message.id }) {
privateChats[targetPeerID]?[idx] = message
} else {
privateChats[targetPeerID]?.append(message)
}
// Sanitize to avoid duplicate IDs
privateChatManager.sanitizeChat(for: targetPeerID)
trimPrivateChatMessagesIfNeeded(for: targetPeerID)
}
@MainActor
private func mirrorToEphemeralIfNeeded(_ message: BitchatMessage, targetPeerID: String, key: Data?) {
guard let key,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id,
ephemeralPeerID != targetPeerID
else {
return
}
if privateChats[ephemeralPeerID] == nil {
privateChats[ephemeralPeerID] = []
}
if let idx = privateChats[ephemeralPeerID]?.firstIndex(where: { $0.id == message.id }) {
privateChats[ephemeralPeerID]?[idx] = message
} else {
privateChats[ephemeralPeerID]?.append(message)
trimPrivateChatMessagesIfNeeded(for: ephemeralPeerID)
}
privateChatManager.sanitizeChat(for: ephemeralPeerID)
}
@MainActor
private func sendDeliveryAckViaNostrEmbedded(_ message: BitchatMessage, wasReadBefore: Bool, senderPubkey: String, key: Data?) {
guard !wasReadBefore else { return }
if let key {
SecureLogger.debug("Sending DELIVERED ack for \(message.id.prefix(8))… via router", category: .session)
messageRouter.sendDeliveryAck(message.id, to: key.hexEncodedString())
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
// Fallback: no Noise mapping yet send directly to sender's Nostr pubkey
let nt = NostrTransport(keychain: keychain)
nt.senderPeerID = meshService.myPeerID
nt.sendDeliveryAckGeohash(for: message.id, toRecipientHex: senderPubkey, from: id)
SecureLogger.debug("Sent DELIVERED ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(message.id.prefix(8))", category: .session)
}
}
@MainActor
private func handleViewingThisChat(_ message: BitchatMessage, targetPeerID: String, key: Data?, senderPubkey: String) {
unreadPrivateMessages.remove(targetPeerID)
if let key,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id {
unreadPrivateMessages.remove(ephemeralPeerID)
}
if !sentReadReceipts.contains(message.id) {
if let key {
let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID, readerNickname: nickname)
SecureLogger.debug("Viewing chat; sending READ ack for \(message.id.prefix(8))… via router", category: .session)
messageRouter.sendReadReceipt(receipt, to: key.hexEncodedString())
sentReadReceipts.insert(message.id)
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
let nt = NostrTransport(keychain: keychain)
nt.senderPeerID = meshService.myPeerID
nt.sendReadReceiptGeohash(message.id, toRecipientHex: senderPubkey, from: id)
sentReadReceipts.insert(message.id)
SecureLogger.debug("Viewing chat; sent READ ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(message.id.prefix(8))", category: .session)
}
}
}
@MainActor
private func markAsUnreadIfNeeded(
shouldMarkAsUnread: Bool,
targetPeerID: String,
key: Data?,
isRecentMessage: Bool,
senderNickname: String,
messageContent: String
) {
guard shouldMarkAsUnread else { return }
unreadPrivateMessages.insert(targetPeerID)
if let key,
let ephemeralPeerID = unifiedPeerService.peers.first(where: { $0.noisePublicKey == key })?.id,
ephemeralPeerID != targetPeerID {
unreadPrivateMessages.insert(ephemeralPeerID)
}
if isRecentMessage {
NotificationService.shared.sendPrivateMessageNotification(
from: senderNickname,
message: messageContent,
peerID: targetPeerID
)
}
}
@MainActor @MainActor
private func handleFavoriteNotification(content: String, from nostrPubkey: String) { private func handleFavoriteNotification(content: String, from nostrPubkey: String) {
let isFavorite = content.hasPrefix("FAVORITED") let isFavorite = content.hasPrefix("FAVORITED")