Add read receipts for private messages

- Added ReadReceipt structure and .readReceipt message type
- Added .read delivery status with blue checkmarks in UI
- Send read receipts when viewing a private chat
- Send read receipts immediately for new messages if chat is open
- Update message status from delivered to read when receipt received
This commit is contained in:
jack
2025-07-06 21:49:56 +02:00
parent 2e56245a7e
commit e2b0632879
4 changed files with 143 additions and 0 deletions
@@ -707,6 +707,41 @@ class BluetoothMeshService: NSObject {
}
}
func sendReadReceipt(_ receipt: ReadReceipt, to recipientID: String) {
messageQueue.async { [weak self] in
guard let self = self else { return }
// Encode the receipt
guard let receiptData = receipt.encode() else {
print("[DeliveryTracker] Failed to encode read receipt")
return
}
// Encrypt receipt for the original sender
let encryptedPayload: Data
do {
encryptedPayload = try self.encryptionService.encrypt(receiptData, for: recipientID)
} catch {
print("[DeliveryTracker] Failed to encrypt read receipt: \(error)")
return
}
// Create read receipt packet with direct routing to original sender
let packet = BitchatPacket(
type: MessageType.readReceipt.rawValue,
senderID: Data(self.myPeerID.utf8),
recipientID: Data(recipientID.utf8),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encryptedPayload,
signature: nil, // Read receipts don't need signatures
ttl: 3 // Limited TTL for receipts
)
// Send immediately without delay
self.broadcastPacket(packet)
}
}
func announcePasswordProtectedRoom(_ room: String, isProtected: Bool = true, creatorID: String? = nil, keyCommitment: String? = nil) {
messageQueue.async { [weak self] in
guard let self = self else { return }
@@ -1968,6 +2003,32 @@ class BluetoothMeshService: NSObject {
}
}
case .readReceipt:
// Handle read receipt
if let recipientIDData = packet.recipientID,
let recipientID = String(data: recipientIDData.trimmingNullBytes(), encoding: .utf8),
recipientID == myPeerID {
// This read receipt is for us - decrypt it
if let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
do {
let decryptedData = try encryptionService.decrypt(packet.payload, from: senderID)
if let receipt = ReadReceipt.decode(from: decryptedData) {
// Process the read receipt
DispatchQueue.main.async {
self.delegate?.didReceiveReadReceipt(receipt)
}
}
} catch {
// Failed to decrypt read receipt - might be from unknown sender
}
}
} else if packet.ttl > 0 {
// Relay the read receipt if not for us
var relayPacket = packet
relayPacket.ttl -= 1
self.broadcastPacket(relayPacket)
}
default:
break
}