mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 22:25:20 +00:00
Refactor/robustness (#446)
* Refactor BitChat for improved robustness and performance Major refactoring to simplify architecture and fix critical issues: Architecture Improvements: - Replace complex BluetoothMeshService with simplified SimplifiedBluetoothService - Consolidate message routing and peer management into unified services - Remove redundant caching layers and optimize performance Bug Fixes: - Fix critical BLE peer mapping corruption in mesh networks - Fix encrypted message routing failures in multi-peer scenarios - Fix app freezes and Main Thread Checker warnings - Fix BLE message delivery in dual-role connections - Fix favorite toggle UI not updating instantly - Fix Nostr offline messaging with 24-hour message filtering Features: - Add command processor for chat commands - Add autocomplete service for mentions and commands - Improve private chat management with dedicated service - Add unified peer service for consistent state management Performance: - Optimize BLE reconnection speed - Reduce excessive logging throughout codebase - Improve message deduplication efficiency - Optimize UI updates and state management * Improvements refactor robust (#441) * remove unused code * remove more * TLV for announcement * restore * restore * restore? * messages tlv too (#442) * Fix Nostr notification and read receipt issues, add TLV encoding, cleanup unused code ## Notification & Read Receipt Fixes - Fixed toolbar notification icon appearing incorrectly on app restart for already-read messages - Fixed read receipts being incorrectly deleted on startup when privateChats was empty - Fixed messages not being marked as read when opening chat for first time - Fixed senderPeerID not being updated during message consolidation - Added startup phase logic to block old messages (>30s) while allowing recent ones - Fixed unread status checking across all storage locations (ephemeral, stable Noise keys, temporary Nostr IDs) ## TLV Encoding Implementation - Implemented Type-Length-Value encoding for private message payloads - Added PrivateMessagePacket struct with TLV encode/decode methods - Enhanced message structure for better extensibility and robustness ## Code Cleanup - Removed unused PeerStateManager class and related dependencies - Removed dead protocol types (DeliveryAck, ProtocolAck/Nack, NoiseIdentityAnnouncement) - Cleaned up BitchatDelegate by removing unused methods - Removed excessive debug logging throughout ChatViewModel - Added test-only ProtocolNack helper for integration tests ## Technical Details - Messages stored under three ID types: ephemeral peer IDs, stable Noise key hexes, temporary Nostr IDs - Fixed cleanupOldReadReceipts() to skip when privateChats is empty or during startup - Updated message consolidation to properly update senderPeerID - Restored NIP-17 timestamp randomization (±15 minutes) for privacy --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
@@ -140,6 +140,16 @@ struct NostrIdentityBridge {
|
||||
}
|
||||
return pubkey
|
||||
}
|
||||
|
||||
/// Clear all Nostr identity associations and current identity
|
||||
static func clearAllAssociations() {
|
||||
// Delete current Nostr identity
|
||||
KeychainHelper.delete(key: currentIdentityKey, service: keychainService)
|
||||
|
||||
// Note: We can't efficiently delete all noise-nostr associations
|
||||
// without tracking them, but they'll be orphaned and eventually cleaned up
|
||||
// The important part is deleting the current identity so a new one is generated
|
||||
}
|
||||
}
|
||||
|
||||
// Bech32 encoding for Nostr (minimal implementation)
|
||||
|
||||
@@ -59,10 +59,11 @@ struct NostrProtocol {
|
||||
}
|
||||
|
||||
/// Decrypt a received NIP-17 message
|
||||
/// Returns the content, sender pubkey, and the actual message timestamp (not the randomized gift wrap timestamp)
|
||||
static func decryptPrivateMessage(
|
||||
giftWrap: NostrEvent,
|
||||
recipientIdentity: NostrIdentity
|
||||
) throws -> (content: String, senderPubkey: String) {
|
||||
) throws -> (content: String, senderPubkey: String, timestamp: Int) {
|
||||
|
||||
// Starting decryption
|
||||
|
||||
@@ -94,7 +95,7 @@ struct NostrProtocol {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (content: rumor.content, senderPubkey: rumor.pubkey)
|
||||
return (content: rumor.content, senderPubkey: rumor.pubkey, timestamp: rumor.created_at)
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
@@ -438,8 +439,9 @@ struct NostrProtocol {
|
||||
|
||||
private static func randomizedTimestamp() -> Date {
|
||||
// Add random offset to current time for privacy
|
||||
// TEMPORARY: Reduced range to debug timestamp issue
|
||||
let offset = TimeInterval.random(in: -60...60) // +/- 1 minute (was +/- 15 minutes)
|
||||
// This prevents timing correlation attacks while the actual message timestamp
|
||||
// is preserved in the encrypted rumor
|
||||
let offset = TimeInterval.random(in: -900...900) // +/- 15 minutes
|
||||
let now = Date()
|
||||
let randomized = now.addingTimeInterval(offset)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class NostrRelayManager: ObservableObject {
|
||||
|
||||
/// Connect to all configured relays
|
||||
func connect() {
|
||||
SecureLogger.log("🌐 Connecting to \(relays.count) Nostr relays", category: SecureLogger.session, level: .debug)
|
||||
for relay in relays {
|
||||
connectToRelay(relay.url)
|
||||
}
|
||||
@@ -96,6 +97,9 @@ class NostrRelayManager: ObservableObject {
|
||||
) {
|
||||
messageHandlers[id] = handler
|
||||
|
||||
SecureLogger.log("📡 Setting up subscription '\(id)' with filter - kinds: \(filter.kinds ?? []), since: \(filter.since ?? 0)",
|
||||
category: SecureLogger.session, level: .debug)
|
||||
|
||||
let req = NostrRequest.subscribe(id: id, filters: [filter])
|
||||
|
||||
do {
|
||||
@@ -107,9 +111,8 @@ class NostrRelayManager: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
// Sending subscription to relays
|
||||
// Filter JSON prepared
|
||||
// Full filter JSON logged
|
||||
// SecureLogger.log("📋 Subscription filter JSON: \(messageString.prefix(200))...",
|
||||
// category: SecureLogger.session, level: .debug)
|
||||
|
||||
// Send subscription to all connected relays
|
||||
for (relayUrl, connection) in connections {
|
||||
@@ -118,6 +121,8 @@ class NostrRelayManager: ObservableObject {
|
||||
SecureLogger.log("❌ Failed to send subscription to \(relayUrl): \(error)",
|
||||
category: SecureLogger.session, level: .error)
|
||||
} else {
|
||||
// SecureLogger.log("✅ Subscription '\(id)' sent to relay: \(relayUrl)",
|
||||
// category: SecureLogger.session, level: .debug)
|
||||
// Subscription sent successfully
|
||||
Task { @MainActor in
|
||||
var subs = self.subscriptions[relayUrl] ?? Set<String>()
|
||||
@@ -188,10 +193,11 @@ class NostrRelayManager: ObservableObject {
|
||||
task.sendPing { [weak self] error in
|
||||
DispatchQueue.main.async {
|
||||
if error == nil {
|
||||
// Successfully connected to Nostr relay
|
||||
SecureLogger.log("✅ Connected to Nostr relay: \(urlString)",
|
||||
category: SecureLogger.session, level: .debug)
|
||||
self?.updateRelayStatus(urlString, isConnected: true)
|
||||
} else {
|
||||
SecureLogger.log("Failed to connect to Nostr relay \(urlString): \(error?.localizedDescription ?? "Unknown error")",
|
||||
SecureLogger.log("❌ Failed to connect to Nostr relay \(urlString): \(error?.localizedDescription ?? "Unknown error")",
|
||||
category: SecureLogger.session, level: .error)
|
||||
self?.updateRelayStatus(urlString, isConnected: false, error: error)
|
||||
// Trigger disconnection handler for proper backoff
|
||||
@@ -254,7 +260,11 @@ class NostrRelayManager: ObservableObject {
|
||||
|
||||
let event = try NostrEvent(from: eventDict)
|
||||
|
||||
// Processing event
|
||||
// Only log critical events
|
||||
if event.kind != 1059 { // Don't log every gift wrap
|
||||
SecureLogger.log("📥 Received Nostr event (kind: \(event.kind)) from relay: \(relayUrl)",
|
||||
category: SecureLogger.session, level: .debug)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Update relay stats
|
||||
@@ -282,7 +292,10 @@ class NostrRelayManager: ObservableObject {
|
||||
let eventId = array[1] as? String,
|
||||
let success = array[2] as? Bool {
|
||||
let reason = array.count >= 4 ? (array[3] as? String ?? "no reason given") : "no reason given"
|
||||
if !success {
|
||||
if success {
|
||||
SecureLogger.log("✅ Event \(eventId.prefix(16))... accepted by relay: \(relayUrl)",
|
||||
category: SecureLogger.session, level: .debug)
|
||||
} else {
|
||||
SecureLogger.log("📮 Event \(eventId) rejected by relay: \(reason)",
|
||||
category: SecureLogger.session, level: .error)
|
||||
}
|
||||
@@ -311,8 +324,8 @@ class NostrRelayManager: ObservableObject {
|
||||
let data = try encoder.encode(req)
|
||||
let message = String(data: data, encoding: .utf8) ?? ""
|
||||
|
||||
// Sending event to relay
|
||||
// Event JSON prepared
|
||||
SecureLogger.log("📤 Sending Nostr event (kind: \(event.kind)) to relay: \(relayUrl)",
|
||||
category: SecureLogger.session, level: .debug)
|
||||
|
||||
connection.send(.string(message)) { [weak self] error in
|
||||
DispatchQueue.main.async {
|
||||
@@ -320,6 +333,8 @@ class NostrRelayManager: ObservableObject {
|
||||
SecureLogger.log("❌ Failed to send event to \(relayUrl): \(error)",
|
||||
category: SecureLogger.session, level: .error)
|
||||
} else {
|
||||
// SecureLogger.log("✅ Event sent to relay: \(relayUrl)",
|
||||
// category: SecureLogger.session, level: .debug)
|
||||
// Update relay stats
|
||||
if let index = self?.relays.firstIndex(where: { $0.url == relayUrl }) {
|
||||
self?.relays[index].messagesSent += 1
|
||||
|
||||
Reference in New Issue
Block a user