From 13d9be90824527ccf09ed13b6bad8a99a5f3458d Mon Sep 17 00:00:00 2001 From: jack Date: Sun, 6 Jul 2025 22:55:51 +0200 Subject: [PATCH] Fix read receipts for existing messages when opening chat Critical fixes: 1. Match messages by sender nickname in addition to peer ID (since peer IDs change) 2. Send read receipts to CURRENT peer ID, not old senderPeerID from message 3. Remove requirement for senderPeerID to be non-nil (blocks older messages) 4. Add extensive logging to trace the flow The core issue was that peer IDs are ephemeral and change between sessions: - Rick sends message with peer ID A - Later, Jack connects to Rick who now has peer ID B - Read receipt needs to be sent to B, not A - Messages need to be matched by nickname, not just peer ID --- DELIVERY_CONFIRMATION_STATUS.md | 37 +++++++++++++ bitchat/ViewModels/ChatViewModel.swift | 76 ++++++++++++-------------- bitchat/Views/ContentView.swift | 4 ++ 3 files changed, 75 insertions(+), 42 deletions(-) create mode 100644 DELIVERY_CONFIRMATION_STATUS.md diff --git a/DELIVERY_CONFIRMATION_STATUS.md b/DELIVERY_CONFIRMATION_STATUS.md new file mode 100644 index 00000000..6e30693d --- /dev/null +++ b/DELIVERY_CONFIRMATION_STATUS.md @@ -0,0 +1,37 @@ +# Delivery Confirmation Feature - Current Status + +## What's Working +1. ✅ Delivery confirmations (green double checkmarks) work correctly +2. ✅ Read receipts are sent and received properly +3. ✅ Blue checkmarks appear for messages that are read while both users are in the chat +4. ✅ Status updates are preserved (read status won't downgrade to delivered) + +## Remaining Issue +When Jack opens a chat with Rick, the first message (that was already delivered before Jack opened the chat) doesn't get a read receipt sent. Only new messages that arrive while the chat is open get read receipts. + +### Root Cause +The `markPrivateMessagesAsRead` function is being called when Jack opens the chat, but: +1. It looks for messages with status "sent" or "delivered" +2. Messages from Rick are stored with Rick's senderPeerID (which changes between sessions) +3. The peer ID mismatch might prevent finding the right messages + +### Solution Needed +Jack needs to send read receipts for ALL unread messages from Rick when opening the chat, regardless of their current peer ID. + +## Test Scenario +1. Rick sends message to Jack while Jack is offline/not in chat +2. Message shows green checkmarks (delivered) on Rick's side +3. Jack opens the chat with Rick +4. **Expected**: Rick's message should turn blue +5. **Actual**: Message stays green until a new message is sent + +## Logs Showing the Issue +``` +Mac (Rick): +- Message F4D69DB6... shows "delivered to jack" (stays green) +- Never receives read receipt for this first message + +Phone (Jack): +- Opens chat but doesn't send read receipt for existing messages +- Only sends read receipts for new messages received while chat is open +``` \ No newline at end of file diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index ef4f2afd..2adf2d4c 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -935,31 +935,31 @@ class ChatViewModel: ObservableObject { return } - print("[Delivery] Checking \(messages.count) messages in chat with peer \(peerID) for read receipts") + // Get the nickname for this peer + let peerNickname = meshService.getPeerNicknames()[peerID] ?? "" + print("[Delivery] Checking \(messages.count) messages in chat with peer \(peerID) (\(peerNickname)) for read receipts") // Find messages from the peer that haven't been read yet for message in messages { // Only send read receipts for messages from the other peer (not our own) - print("[Delivery] Message \(message.id) from \(message.sender), senderPeerID: \(message.senderPeerID ?? "nil"), myNickname: \(nickname), status: \(message.deliveryStatus?.displayText ?? "none")") + print("[Delivery] Message \(message.id) from \(message.sender), senderPeerID: \(message.senderPeerID ?? "nil"), currentPeerID: \(peerID), myNickname: \(nickname), status: \(message.deliveryStatus?.displayText ?? "none")") // Check if this is a message FROM the other person TO us - if message.sender != nickname && message.senderPeerID != nil { + // Match by sender nickname since peer IDs change between sessions + let isFromPeer = message.sender == peerNickname || message.senderPeerID == peerID + if message.sender != nickname && isFromPeer { if let status = message.deliveryStatus { switch status { case .sent, .delivered: // Create and send read receipt for sent or delivered messages - // Use the senderPeerID from the message to ensure it goes to the right peer - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending read receipt for message \(message.id) to peer \(messageSenderID)") - } else { - print("[Delivery] Cannot send read receipt for message \(message.id) - no senderPeerID") - } + // Send to the CURRENT peer ID, not the old senderPeerID which may have changed + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending read receipt for message \(message.id) from \(message.sender) to current peer \(peerID)") case .read: // Already read, no need to send another receipt print("[Delivery] Message \(message.id) already marked as read") @@ -973,17 +973,13 @@ class ChatViewModel: ObservableObject { // No delivery status - this might be an older message // Send read receipt anyway for backwards compatibility print("[Delivery] Message \(message.id) has no delivery status, sending read receipt anyway") - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending read receipt for old message \(message.id) to peer \(messageSenderID)") - } else { - print("[Delivery] Cannot send read receipt for old message \(message.id) - no senderPeerID") - } + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending read receipt for old message \(message.id) to current peer \(peerID)") } } } @@ -1860,22 +1856,18 @@ extension ChatViewModel: BitchatDelegate { unreadPrivateMessages.remove(peerID) // Send read receipt immediately since we're viewing the chat - // Use the senderPeerID from the message, not the current peerID - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending immediate read receipt for message \(message.id) from \(message.sender) to peer \(messageSenderID)") - - // Also check if there are other unread messages from this peer - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in - self?.markPrivateMessagesAsRead(from: peerID) - } - } else { - print("[Delivery] Cannot send read receipt - message has no senderPeerID") + // Send to the current peer ID since peer IDs change between sessions + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending immediate read receipt for message \(message.id) from \(message.sender) to current peer \(peerID)") + + // Also check if there are other unread messages from this peer + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + self?.markPrivateMessagesAsRead(from: peerID) } } } else if message.sender == nickname { diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index c2a15e00..603ed542 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -531,8 +531,10 @@ struct ContentView: View { .onChange(of: viewModel.selectedPrivateChatPeer) { newPeerID in // When switching to a private chat, send read receipts if let peerID = newPeerID { + print("[UI] Selected private chat peer changed to \(peerID)") // Small delay to ensure messages are loaded DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + print("[UI] Triggering markPrivateMessagesAsRead for peer \(peerID)") viewModel.markPrivateMessagesAsRead(from: peerID) } } @@ -540,7 +542,9 @@ struct ContentView: View { .onAppear { // Also check when view appears if let peerID = viewModel.selectedPrivateChatPeer { + print("[UI] Messages view appeared with selected peer \(peerID)") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + print("[UI] Triggering markPrivateMessagesAsRead on appear for peer \(peerID)") viewModel.markPrivateMessagesAsRead(from: peerID) } }