mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 00:25:20 +00:00
Refactor/ble nostr boundaries (#449)
* Refactor: move Nostr embedding and TLVs out of BLE; add NostrEmbeddedBitChat, Packets, PeerIDUtils; centralize MessageDeduplicator; update ChatViewModel to use new helpers; remove AI_CONTEXT.md and CLAUDE.md * Rename class to BLEService with compatibility alias; move mention parsing out of BLE; emit low-level BLE events to delegate; unify hex helpers; accept 64-hex in isPeerConnected; add PeerIDResolver * Project: rename file to BLEService.swift and update Xcode project; keep typealias SimplifiedBluetoothService = BLEService for compatibility * Remove SimplifiedBluetoothService alias; update app code to use BLEService explicitly * Tests: rename MockSimplifiedBluetoothService to MockBLEService; update typealiases and Xcode project * Docs: update comments to refer to BLEService (tests, protocol, noise service) * Tests: rename SimplifiedBluetoothServiceTests to BLEServiceTests; update project references and class names * Introduce Transport protocol; BLEService conforms; document delegate-only event pattern in BLEService; keep publishers internal for UnifiedPeerService * Adopt Transport end-to-end: add TransportPeerSnapshot + publishers; BLEService maps to Transport snapshots; UnifiedPeerService consumes Transport; ChatViewModel holds Transport * Fix Transport integration: replace getPeerFingerprint with getFingerprint(for:); update PrivateChatManager and CommandProcessor to use Transport; add BLEService.getFingerprint(for:); update PeerManager to use Transport * Refactor transport and BLE/Nostr layers; unify UI events; fix MainActor isolation - Rename SimplifiedBluetoothService to BLEService and slim responsibilities - Introduce Transport protocol and peerEventsDelegate for UI updates - Add NostrTransport and MessageRouter to route PM/read/favorite via BLE or Nostr - Centralize TLVs, PeerID utils, and MessageDeduplicator outside BLE - Update UnifiedPeerService and ChatViewModel to use Transport and delegate events - Fix MainActor isolation: route delegate calls via Task on MainActor; update notifyUI helper - Adjust related files and tests accordingly * BLEService: remove internal publishers; switch to delegate-only events - Drop legacy messages/peers/fullPeers publishers - Provide lightweight peerSnapshotSubject only to satisfy Transport - Rework publishFullPeerData to build snapshots from internal state and notify delegate + subject - Remove all peersPublisher.send call sites - Keep UnifiedPeerService on delegate updates exclusively * Remove inlined Nostr send helpers from ChatViewModel; route via MessageRouter - Replace direct Nostr sends (PM, ACKs, favorites) with MessageRouter - Add router method for delivery ACKs and implement NostrTransport.sendDeliveryAck - Simplify ChatViewModel favorite notification path to use router - Keep Nostr receive handling intact; reduce duplication * Fix ReadReceipt initializer usage in ChatViewModel (readerID + readerNickname) * Fix unused variable warning: replace shadowed 'nostrPubkey' bind with boolean check in ChatViewModel * Fix queued PM format: use TLV for pending messages after Noise handshake - Pending messages (including first-time favorite notifications) now use the same TLV encoding as normal sends - Ensures ChatViewModel can decode on first send, even if handshake completes after queuing --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Message Deduplicator (shared)
|
||||
|
||||
final class MessageDeduplicator {
|
||||
private struct Entry {
|
||||
let messageID: String
|
||||
let timestamp: Date
|
||||
}
|
||||
|
||||
private var entries: [Entry] = []
|
||||
private var lookup = Set<String>()
|
||||
private let lock = NSLock()
|
||||
private let maxAge: TimeInterval = 300 // 5 minutes
|
||||
private let maxCount = 1000
|
||||
|
||||
/// Check if message is duplicate and add if not
|
||||
func isDuplicate(_ messageID: String) -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
cleanupOldEntries()
|
||||
|
||||
if lookup.contains(messageID) {
|
||||
return true
|
||||
}
|
||||
|
||||
entries.append(Entry(messageID: messageID, timestamp: Date()))
|
||||
lookup.insert(messageID)
|
||||
|
||||
if entries.count > maxCount {
|
||||
let toRemove = entries.prefix(100)
|
||||
toRemove.forEach { lookup.remove($0.messageID) }
|
||||
entries.removeFirst(100)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// Add an ID without checking (for announce-back tracking)
|
||||
func markProcessed(_ messageID: String) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
if !lookup.contains(messageID) {
|
||||
entries.append(Entry(messageID: messageID, timestamp: Date()))
|
||||
lookup.insert(messageID)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if ID exists without adding
|
||||
func contains(_ messageID: String) -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return lookup.contains(messageID)
|
||||
}
|
||||
|
||||
/// Clear all entries
|
||||
func reset() {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
entries.removeAll()
|
||||
lookup.removeAll()
|
||||
}
|
||||
|
||||
/// Periodic cleanup
|
||||
func cleanup() {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
cleanupOldEntries()
|
||||
|
||||
if entries.capacity > maxCount * 2 {
|
||||
entries.reserveCapacity(maxCount)
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanupOldEntries() {
|
||||
let cutoff = Date().addingTimeInterval(-maxAge)
|
||||
while let first = entries.first, first.timestamp < cutoff {
|
||||
lookup.remove(first.messageID)
|
||||
entries.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
|
||||
struct PeerIDResolver {
|
||||
/// Returns a 16-hex short peer ID derived from a 64-hex Noise public key if needed
|
||||
static func toShortID(_ id: String) -> String {
|
||||
if id.count == 64, let data = Data(hexString: id) {
|
||||
return PeerIDUtils.derivePeerID(fromPublicKey: data)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
static func isShortID(_ id: String) -> Bool {
|
||||
return id.count == 16 && Data(hexString: id) != nil
|
||||
}
|
||||
|
||||
static func isNoiseKeyHex(_ id: String) -> Bool {
|
||||
return id.count == 64 && Data(hexString: id) != nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user