mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 09:05:20 +00:00
Extract processNostrMessage into a function
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user