mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 05:05:19 +00:00
* Rearrange `Transport`’s properties and functions * `NostrTransport`: group Transport-related code together * `BLEService`: group Transport-related code together * Extract `NotificationStreamAssembler` into a file * Move private functions to a dedicated extension * PeerID 14/n: `Transport` and its dependents
124 lines
4.0 KiB
Swift
124 lines
4.0 KiB
Swift
//
|
|
// PrivateChatManager.swift
|
|
// bitchat
|
|
//
|
|
// Manages private chat sessions and messages
|
|
// This is free and unencumbered software released into the public domain.
|
|
//
|
|
|
|
import BitLogger
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
/// Manages all private chat functionality
|
|
final class PrivateChatManager: ObservableObject {
|
|
@Published var privateChats: [String: [BitchatMessage]] = [:]
|
|
@Published var selectedPeer: String? = nil
|
|
@Published var unreadMessages: Set<String> = []
|
|
|
|
private var selectedPeerFingerprint: String? = nil
|
|
var sentReadReceipts: Set<String> = [] // Made accessible for ChatViewModel
|
|
|
|
weak var meshService: Transport?
|
|
// Route acks/receipts via MessageRouter (chooses mesh or Nostr)
|
|
weak var messageRouter: MessageRouter?
|
|
|
|
init(meshService: Transport? = nil) {
|
|
self.meshService = meshService
|
|
}
|
|
|
|
// Cap for messages stored per private chat
|
|
private let privateChatCap = TransportConfig.privateChatCap
|
|
|
|
/// Start a private chat with a peer
|
|
func startChat(with peerID: String) {
|
|
selectedPeer = peerID
|
|
|
|
// Store fingerprint for persistence across reconnections
|
|
if let fingerprint = meshService?.getFingerprint(for: PeerID(str: peerID)) {
|
|
selectedPeerFingerprint = fingerprint
|
|
}
|
|
|
|
// Mark messages as read
|
|
markAsRead(from: peerID)
|
|
|
|
// Initialize chat if needed
|
|
if privateChats[peerID] == nil {
|
|
privateChats[peerID] = []
|
|
}
|
|
}
|
|
|
|
/// End the current private chat
|
|
func endChat() {
|
|
selectedPeer = nil
|
|
selectedPeerFingerprint = nil
|
|
}
|
|
|
|
/// Remove duplicate messages by ID and keep chronological order
|
|
func sanitizeChat(for peerID: String) {
|
|
guard let arr = privateChats[peerID] else { return }
|
|
if arr.count <= 1 {
|
|
return
|
|
}
|
|
|
|
var indexByID: [String: Int] = [:]
|
|
indexByID.reserveCapacity(arr.count)
|
|
var deduped: [BitchatMessage] = []
|
|
deduped.reserveCapacity(arr.count)
|
|
|
|
for msg in arr.sorted(by: { $0.timestamp < $1.timestamp }) {
|
|
if let existing = indexByID[msg.id] {
|
|
deduped[existing] = msg
|
|
} else {
|
|
indexByID[msg.id] = deduped.count
|
|
deduped.append(msg)
|
|
}
|
|
}
|
|
|
|
privateChats[peerID] = deduped
|
|
}
|
|
|
|
/// Mark messages from a peer as read
|
|
func markAsRead(from peerID: String) {
|
|
unreadMessages.remove(peerID)
|
|
|
|
// Send read receipts for unread messages that haven't been sent yet
|
|
if let messages = privateChats[peerID] {
|
|
for message in messages {
|
|
if message.senderPeerID == peerID && !message.isRelay && !sentReadReceipts.contains(message.id) {
|
|
sendReadReceipt(for: message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func sendReadReceipt(for message: BitchatMessage) {
|
|
guard !sentReadReceipts.contains(message.id),
|
|
let senderPeerID = message.senderPeerID else {
|
|
return
|
|
}
|
|
|
|
sentReadReceipts.insert(message.id)
|
|
|
|
// Create read receipt using the simplified method
|
|
let receipt = ReadReceipt(
|
|
originalMessageID: message.id,
|
|
readerID: meshService?.myPeerID.id ?? "",
|
|
readerNickname: meshService?.myNickname ?? ""
|
|
)
|
|
|
|
// Route via MessageRouter to avoid handshakeRequired spam when session isn't established
|
|
if let router = messageRouter {
|
|
SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.id.prefix(8))… via router", category: .session)
|
|
Task { @MainActor in
|
|
router.sendReadReceipt(receipt, to: senderPeerID)
|
|
}
|
|
} else {
|
|
// Fallback: preserve previous behavior
|
|
meshService?.sendReadReceipt(receipt, to: senderPeerID)
|
|
}
|
|
}
|
|
}
|