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:
jack
2025-08-17 15:17:04 +02:00
committed by GitHub
co-authored by jack
parent 3ebfa85e90
commit 6fbf7eee25
25 changed files with 873 additions and 1647 deletions
+230
View File
@@ -0,0 +1,230 @@
//
// MockBLEService.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Foundation
import CoreBluetooth
@testable import bitchat
// Mock implementation that mimics BLEService behavior
class MockBLEService: NSObject {
// MARK: - Properties matching BLEService
weak var delegate: BitchatDelegate?
var myPeerID: String = "MOCK1234"
var myNickname: String = "MockUser"
// Test-specific properties
var sentMessages: [(message: BitchatMessage, packet: BitchatPacket)] = []
var sentPackets: [BitchatPacket] = []
var connectedPeers: Set<String> = []
var messageDeliveryHandler: ((BitchatMessage) -> Void)?
var packetDeliveryHandler: ((BitchatPacket) -> Void)?
// Compatibility properties for old tests
var mockNickname: String {
get { return myNickname }
set { myNickname = newValue }
}
var nickname: String {
return myNickname
}
var peerID: String {
return myPeerID
}
// MARK: - Initialization
override init() {
super.init()
}
// MARK: - Methods matching BLEService
func setNickname(_ nickname: String) {
self.myNickname = nickname
}
func startServices() {
// Mock implementation - do nothing
}
func stopServices() {
// Mock implementation - do nothing
}
func isPeerConnected(_ peerID: String) -> Bool {
return connectedPeers.contains(peerID)
}
func getPeerNicknames() -> [String: String] {
var nicknames: [String: String] = [:]
for peer in connectedPeers {
nicknames[peer] = "MockPeer_\(peer)"
}
return nicknames
}
func getPeers() -> [String: String] {
return getPeerNicknames()
}
func sendMessage(_ content: String, mentions: [String] = [], to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
let message = BitchatMessage(
id: messageID ?? UUID().uuidString,
sender: myNickname,
content: content,
timestamp: timestamp ?? Date(),
isRelay: false,
originalSender: nil,
isPrivate: recipientID != nil,
recipientNickname: nil,
senderPeerID: myPeerID,
mentions: mentions.isEmpty ? nil : mentions
)
if let payload = message.toBinaryPayload() {
let packet = BitchatPacket(
type: 0x01,
senderID: myPeerID.data(using: .utf8)!,
recipientID: recipientID?.data(using: .utf8),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: nil,
ttl: 3
)
sentMessages.append((message, packet))
sentPackets.append(packet)
// Simulate local echo
DispatchQueue.main.async { [weak self] in
self?.delegate?.didReceiveMessage(message)
}
// Call delivery handler if set
messageDeliveryHandler?(message)
}
}
func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String) {
let message = BitchatMessage(
id: messageID,
sender: myNickname,
content: content,
timestamp: Date(),
isRelay: false,
originalSender: nil,
isPrivate: true,
recipientNickname: recipientNickname,
senderPeerID: myPeerID,
mentions: nil
)
if let payload = message.toBinaryPayload() {
let packet = BitchatPacket(
type: 0x01,
senderID: myPeerID.data(using: .utf8)!,
recipientID: recipientPeerID.data(using: .utf8)!,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: nil,
ttl: 3
)
sentMessages.append((message, packet))
sentPackets.append(packet)
// Simulate local echo
DispatchQueue.main.async { [weak self] in
self?.delegate?.didReceiveMessage(message)
}
// Call delivery handler if set
messageDeliveryHandler?(message)
}
}
func sendFavoriteNotification(to peerID: String, isFavorite: Bool) {
// Mock implementation
}
func sendReadReceipt(_ receipt: ReadReceipt, to peerID: String) {
// Mock implementation
}
func sendBroadcastAnnounce() {
// Mock implementation
}
func getPeerFingerprint(_ peerID: String) -> String? {
return nil
}
func getNoiseSessionState(for peerID: String) -> LazyHandshakeState {
return .none
}
func triggerHandshake(with peerID: String) {
// Mock implementation
}
func emergencyDisconnectAll() {
connectedPeers.removeAll()
delegate?.didUpdatePeerList([])
}
func getNoiseService() -> NoiseEncryptionService {
return NoiseEncryptionService()
}
func getFingerprint(for peerID: String) -> String? {
return nil
}
// MARK: - Test Helper Methods
func simulateConnectedPeer(_ peerID: String) {
connectedPeers.insert(peerID)
delegate?.didConnectToPeer(peerID)
delegate?.didUpdatePeerList(Array(connectedPeers))
}
func simulateDisconnectedPeer(_ peerID: String) {
connectedPeers.remove(peerID)
delegate?.didDisconnectFromPeer(peerID)
delegate?.didUpdatePeerList(Array(connectedPeers))
}
func simulateIncomingMessage(_ message: BitchatMessage) {
delegate?.didReceiveMessage(message)
}
func simulateIncomingPacket(_ packet: BitchatPacket) {
// Process through the actual handling logic
if let message = BitchatMessage.fromBinaryPayload(packet.payload) {
delegate?.didReceiveMessage(message)
}
packetDeliveryHandler?(packet)
}
func getConnectedPeers() -> [String] {
return Array(connectedPeers)
}
// MARK: - Compatibility methods for old tests
func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String? = nil) {
sendPrivateMessage(content, to: recipientPeerID, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString)
}
}
// Backward compatibility for older tests
typealias MockSimplifiedBluetoothService = MockBLEService