Merge pull request #961 from permissionlesstech/chore/cleanup-dead-code

Remove dead code and stabilize tests
This commit is contained in:
jack
2026-01-16 08:42:10 -10:00
committed by GitHub
10 changed files with 28 additions and 213 deletions
-3
View File
@@ -69,7 +69,6 @@ final class NostrRelayManager: ObservableObject {
private var messageQueue: [PendingSend] = []
private let messageQueueLock = NSLock()
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
private var networkService: NetworkActivationService { NetworkActivationService.shared }
private var shouldUseTor: Bool { networkService.userTorEnabled }
@@ -79,8 +78,6 @@ final class NostrRelayManager: ObservableObject {
private let backoffMultiplier: Double = TransportConfig.nostrRelayBackoffMultiplier
private let maxReconnectAttempts = TransportConfig.nostrRelayMaxReconnectAttempts
// Reconnection timer
private var reconnectionTimer: Timer?
// Bump generation to invalidate scheduled reconnects when we reset/disconnect
private var connectionGeneration: Int = 0
+1 -55
View File
@@ -138,11 +138,6 @@ final class BLEService: NSObject {
private var pendingMessagesAfterHandshake: [PeerID: [(content: String, messageID: String)]] = [:]
// Noise typed payloads (ACKs, read receipts, etc.) pending handshake
private var pendingNoisePayloadsAfterHandshake: [PeerID: [Data]] = [:]
// Keep a tiny buffer of the last few unique announces we've seen (by sender)
private var recentAnnounceBySender: [PeerID: BitchatPacket] = [:]
private var recentAnnounceOrder: [PeerID] = []
private let recentAnnounceBufferCap = 3
// Queue for notifications that failed due to full queue
private var pendingNotifications: [(data: Data, centrals: [CBCentral]?)] = []
@@ -354,8 +349,6 @@ final class BLEService: NSObject {
}
collectionsQueue.sync(flags: .barrier) {
recentAnnounceBySender.removeAll()
recentAnnounceOrder.removeAll()
pendingPeripheralWrites.removeAll()
pendingFragmentTransfers.removeAll()
pendingNotifications.removeAll()
@@ -1053,34 +1046,6 @@ final class BLEService: NSObject {
}
}
private func rebroadcastRecentAnnounces() {
// Snapshot sender order to preserve ordering and avoid holding locks while sending
let packets: [BitchatPacket] = collectionsQueue.sync {
recentAnnounceOrder.compactMap { recentAnnounceBySender[$0] }
}
guard !packets.isEmpty else { return }
for (idx, pkt) in packets.enumerated() {
// Stagger slightly to avoid bursts
let delayMs = idx * 20
messageQueue.asyncAfter(deadline: .now() + .milliseconds(delayMs)) { [weak self] in
self?.broadcastPacket(pkt)
}
}
}
private func sendData(_ data: Data, to peripheral: CBPeripheral) {
// Fire-and-forget: Simple send without complex fallback logic
guard peripheral.state == .connected else { return }
let peripheralUUID = peripheral.identifier.uuidString
guard let state = peripherals[peripheralUUID],
let characteristic = state.characteristic else { return }
// Fire-and-forget principle: always use .withoutResponse for speed
// CoreBluetooth will handle fragmentation at L2CAP layer
writeOrEnqueue(data, to: peripheral, characteristic: characteristic, priority: .high)
}
private func handleFileTransfer(_ packet: BitchatPacket, from peerID: PeerID) {
if peerID == myPeerID && packet.ttl != 0 { return }
@@ -1474,17 +1439,6 @@ final class BLEService: NSObject {
}
}
private func sendLeave() {
SecureLogger.debug("👋 Sending leave announcement", category: .session)
let packet = BitchatPacket(
type: MessageType.leave.rawValue,
ttl: messageTTL,
senderID: myPeerID,
payload: Data(myNickname.utf8)
)
broadcastPacket(packet)
}
private func sendAnnounce(forceSend: Bool = false) {
// Throttle announces to prevent flooding
let now = Date()
@@ -2059,8 +2013,6 @@ extension BLEService: CBPeripheralDelegate {
self?.sendAnnounce(forceSend: true)
// Try flushing any spooled directed packets now that we have a link
self?.flushDirectedSpool()
// Rebroadcast a couple of recent announces to seed the new link
self?.rebroadcastRecentAnnounces()
}
} else {
SecureLogger.warning("⚠️ Characteristic does not support notifications", category: .session)
@@ -2302,10 +2254,9 @@ extension BLEService: CBPeripheralManagerDelegate {
return
}
// Still flush directed packets and rebroadcast for legitimate mesh operation
// Still flush directed packets for legitimate mesh operation
messageQueue.asyncAfter(deadline: .now() + TransportConfig.blePostAnnounceDelaySeconds) { [weak self] in
self?.flushDirectedSpool()
self?.rebroadcastRecentAnnounces()
}
return
}
@@ -2331,8 +2282,6 @@ extension BLEService: CBPeripheralManagerDelegate {
self?.sendAnnounce(forceSend: true)
// Flush any spooled directed packets now that we have a central subscribed
self?.flushDirectedSpool()
// Rebroadcast a couple of recent announces to seed the new link
self?.rebroadcastRecentAnnounces()
}
}
@@ -3715,9 +3664,6 @@ extension BLEService {
claimedNickname: announcement.nickname
)
// Record this announce for lightweight rebroadcast buffer (exclude self)
// (recentAnnounces has been removed in the refactor)
// Notify UI on main thread
notifyUI { [weak self] in
guard let self = self else { return }
@@ -31,9 +31,6 @@ final class FavoritesPersistenceService: ObservableObject {
@Published private(set) var favorites: [Data: FavoriteRelationship] = [:] // Noise pubkey -> relationship
@Published private(set) var mutualFavorites: Set<Data> = []
private let userDefaults = UserDefaults.standard
private var cancellables = Set<AnyCancellable>()
static let shared = FavoritesPersistenceService()
init(keychain: KeychainManagerProtocol = KeychainManager()) {
-58
View File
@@ -279,8 +279,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
var geoNicknames: [String: String] = [:] // pubkeyHex(lowercased) -> nickname
// Show Tor status once per app launch
var torStatusAnnounced = false
private var torProgressCancellable: AnyCancellable?
private var lastTorProgressAnnounced = -1
// Track whether a Tor restart is pending so we only announce
// "tor restarted" after an actual restart, not the first launch.
var torRestartPending: Bool = false
@@ -323,8 +321,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
)
// Channel activity tracking for background nudges
var lastPublicActivityAt: [String: Date] = [:] // channelKey -> last activity time
private var lastPublicActivityNotifyAt: [String: Date] = [:]
private let channelInactivityThreshold: TimeInterval = TransportConfig.uiChannelInactivityThresholdSeconds
// Geohash participant tracker
let participantTracker = GeohashParticipantTracker(activityCutoff: -TransportConfig.uiRecentCutoffFiveMinutesSeconds)
// Participants who indicated they teleported (by tag in their events)
@@ -503,8 +499,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
addGeohashOnlySystemMessage(
String(localized: "system.tor.starting", comment: "System message when Tor is starting")
)
// Suppress incremental Tor progress messages
torProgressCancellable = nil
} else if !TorManager.shared.torEnforced && !torStatusAnnounced {
torStatusAnnounced = true
addGeohashOnlySystemMessage(
@@ -631,8 +625,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
object: nil
)
// Listen for delivery acknowledgments
// When app becomes active, send read receipts for visible messages
#if os(macOS)
NotificationCenter.default.addObserver(
@@ -1468,56 +1460,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
selectedPrivateChatFingerprint = nil
}
// MARK: - Nostr Message Handling
@MainActor
@objc private func handleNostrMessage(_ notification: Notification) {
guard let message = notification.userInfo?["message"] as? BitchatMessage else { return }
// Store the Nostr pubkey if provided (for messages from unknown senders)
if let nostrPubkey = notification.userInfo?["nostrPubkey"] as? String,
let senderPeerID = message.senderPeerID {
// Store mapping for read receipts
nostrKeyMapping[senderPeerID] = nostrPubkey
}
// Process the Nostr message through the same flow as Bluetooth messages
didReceiveMessage(message)
}
@objc private func handleDeliveryAcknowledgment(_ notification: Notification) {
guard let messageId = notification.userInfo?["messageId"] as? String else { return }
// Update the delivery status for the message
if let index = messages.firstIndex(where: { $0.id == messageId }) {
// Update delivery status to delivered
messages[index].deliveryStatus = DeliveryStatus.delivered(to: "nostr", at: Date())
// Schedule UI update for delivery status
// UI will update automatically
}
// Also update in private chats if it's a private message
for (peerID, chatMessages) in privateChats {
if let index = chatMessages.firstIndex(where: { $0.id == messageId }) {
privateChats[peerID]?[index].deliveryStatus = DeliveryStatus.delivered(to: "nostr", at: Date())
// UI will update automatically
break
}
}
}
@objc private func handleNostrReadReceipt(_ notification: Notification) {
guard let receipt = notification.userInfo?["receipt"] as? ReadReceipt else { return }
SecureLogger.info("📖 Handling read receipt for message \(receipt.originalMessageID) from Nostr", category: .session)
// Process the read receipt through the same flow as Bluetooth read receipts
didReceiveReadReceipt(receipt)
}
@MainActor
@objc private func handlePeerStatusUpdate(_ notification: Notification) {
// Update private chat peer if needed when peer status changes
+13 -1
View File
@@ -16,9 +16,21 @@ struct PaymentChipView: View {
case cashu(String)
case lightning(String)
private static let cashuAllowedCharacters = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_"))
private static func cashuURL(from link: String) -> URL? {
if let url = URL(string: link), url.scheme != nil {
return url
}
let enc = link.addingPercentEncoding(withAllowedCharacters: cashuAllowedCharacters) ?? link
return URL(string: "cashu:\(enc)")
}
var url: URL? {
switch self {
case .cashu(let link), .lightning(let link):
case .cashu(let link):
return Self.cashuURL(from: link)
case .lightning(let link):
return URL(string: link)
}
}
+3 -68
View File
@@ -40,7 +40,6 @@ struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) private var dismiss
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
@State private var showPeerList = false
@State private var showSidebar = false
@State private var showAppInfo = false
@State private var showMessageActions = false
@@ -57,7 +56,6 @@ struct ContentView: View {
@State private var expandedMessageIDs: Set<String> = []
@State private var showLocationNotes = false
@State private var notesGeohash: String? = nil
@State private var sheetNotesCount: Int = 0
@State private var imagePreviewURL: URL? = nil
@State private var recordingAlertMessage: String = ""
@State private var showRecordingAlert = false
@@ -1181,19 +1179,6 @@ struct ContentView: View {
)
}
// Split a name into base and a '#abcd' suffix if present
private func splitNameSuffix(_ name: String) -> (base: String, suffix: String) {
guard name.count >= 5 else { return (name, "") }
let suffix = String(name.suffix(5))
if suffix.first == "#", suffix.dropFirst().allSatisfy({ c in
("0"..."9").contains(String(c)) || ("a"..."f").contains(String(c)) || ("A"..."F").contains(String(c))
}) {
let base = String(name.dropLast(5))
return (base, suffix)
}
return (name, "")
}
// Compute channel-aware people count and color for toolbar (cross-platform)
private func channelPeopleCountAndColor() -> (Int, Color) {
switch locationManager.selectedChannel {
@@ -1308,8 +1293,8 @@ struct ContentView: View {
// Bookmark toggle (geochats): to the left of #geohash
if case .location(let ch) = locationManager.selectedChannel {
Button(action: { GeohashBookmarksStore.shared.toggle(ch.geohash) }) {
Image(systemName: GeohashBookmarksStore.shared.isBookmarked(ch.geohash) ? "bookmark.fill" : "bookmark")
Button(action: { bookmarks.toggle(ch.geohash) }) {
Image(systemName: bookmarks.isBookmarked(ch.geohash) ? "bookmark.fill" : "bookmark")
.font(.bitchatSystem(size: 12))
}
.buttonStyle(.plain)
@@ -1567,7 +1552,7 @@ private extension ContentView {
} else if let media = mediaAttachment(for: message) {
mediaMessageRow(message: message, media: media)
} else {
textMessageRow(message)
TextMessageView(message: message, expandedMessageIDs: $expandedMessageIDs)
}
}
@@ -1631,56 +1616,6 @@ private extension ContentView {
.padding(.vertical, 4)
}
@ViewBuilder
private func textMessageRow(_ message: BitchatMessage) -> some View {
let cashuTokens = message.content.extractCashuLinks()
let lightningLinks = message.content.extractLightningLinks()
let isLong = (message.content.count > TransportConfig.uiLongMessageLengthThreshold || message.content.hasVeryLongToken(threshold: TransportConfig.uiVeryLongTokenThreshold)) && cashuTokens.isEmpty
let isExpanded = expandedMessageIDs.contains(message.id)
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top, spacing: 0) {
Text(viewModel.formatMessageAsText(message, colorScheme: colorScheme))
.fixedSize(horizontal: false, vertical: true)
.lineLimit(isLong && !isExpanded ? TransportConfig.uiLongMessageLineLimit : nil)
.frame(maxWidth: .infinity, alignment: .leading)
if message.isPrivate && message.sender == viewModel.nickname,
let status = message.deliveryStatus {
DeliveryStatusView(status: status)
.padding(.leading, 4)
}
}
if isLong && cashuTokens.isEmpty {
let labelKey = isExpanded ? LocalizedStringKey("content.message.show_less") : LocalizedStringKey("content.message.show_more")
Button(labelKey) {
if isExpanded { expandedMessageIDs.remove(message.id) }
else { expandedMessageIDs.insert(message.id) }
}
.font(.bitchatSystem(size: 11, weight: .medium, design: .monospaced))
.foregroundColor(Color.blue)
.padding(.top, 4)
}
if !lightningLinks.isEmpty || !cashuTokens.isEmpty {
HStack(spacing: 8) {
ForEach(Array(lightningLinks.prefix(3)), id: \.self) { link in
PaymentChipView(paymentType: .lightning(link))
}
ForEach(Array(cashuTokens.prefix(3)), id: \.self) { token in
let enc = token.addingPercentEncoding(withAllowedCharacters: .alphanumerics.union(CharacterSet(charactersIn: "-_"))) ?? token
let urlStr = "cashu:\(enc)"
PaymentChipView(paymentType: .cashu(urlStr))
}
}
.padding(.top, 6)
.padding(.leading, 2)
}
}
}
private func expandWindow(ifNeededFor message: BitchatMessage,
allMessages: [BitchatMessage],
privatePeer: PeerID?,
+4 -19
View File
@@ -5,21 +5,6 @@
import Foundation
private enum RegexCache {
static let cashu: NSRegularExpression = {
try! NSRegularExpression(pattern: "\\bcashu[AB][A-Za-z0-9._-]{40,}\\b", options: [])
}()
static let lightningScheme: NSRegularExpression = {
try! NSRegularExpression(pattern: "(?i)\\blightning:[^\\s]+", options: [])
}()
static let bolt11: NSRegularExpression = {
try! NSRegularExpression(pattern: "(?i)\\bln(bc|tb|bcrt)[0-9][a-z0-9]{50,}\\b", options: [])
}()
static let lnurl: NSRegularExpression = {
try! NSRegularExpression(pattern: "(?i)\\blnurl1[a-z0-9]{20,}\\b", options: [])
}()
}
extension String {
// Detect if there is an extremely long token (no whitespace/newlines) that could break layout
func hasVeryLongToken(threshold: Int) -> Bool {
@@ -38,7 +23,7 @@ extension String {
// Extract up to `max` Cashu tokens (cashuA/cashuB). Allow dot '.' and shorter lengths.
func extractCashuLinks(max: Int = 3) -> [String] {
let regex = RegexCache.cashu
let regex = MessageFormattingEngine.Patterns.cashu
let ns = self as NSString
let range = NSRange(location: 0, length: ns.length)
var found: [String] = []
@@ -59,19 +44,19 @@ extension String {
let ns = self as NSString
let full = NSRange(location: 0, length: ns.length)
// lightning: scheme
for m in RegexCache.lightningScheme.matches(in: self, range: full) {
for m in MessageFormattingEngine.Patterns.lightningScheme.matches(in: self, range: full) {
let s = ns.substring(with: m.range(at: 0))
results.append(s)
if results.count >= max { return results }
}
// BOLT11
for m in RegexCache.bolt11.matches(in: self, range: full) {
for m in MessageFormattingEngine.Patterns.bolt11.matches(in: self, range: full) {
let s = ns.substring(with: m.range(at: 0))
results.append("lightning:\(s)")
if results.count >= max { return results }
}
// LNURL bech32
for m in RegexCache.lnurl.matches(in: self, range: full) {
for m in MessageFormattingEngine.Patterns.lnurl.matches(in: self, range: full) {
let s = ns.substring(with: m.range(at: 0))
results.append("lightning:\(s)")
if results.count >= max { return results }
@@ -134,7 +134,7 @@ struct FragmentationTests {
}
}
try await sleep(1.0)
try await capture.waitForReceivedMessages(count: 1, timeout: .seconds(2))
let message = try #require(capture.receivedMessages.first, "Expected file transfer message")
#expect(message.content.hasPrefix("[file]"))
+3 -2
View File
@@ -13,6 +13,7 @@ struct GossipSyncManagerTests {
try await confirmation("sync request sent") { sent in
delegate.onSend = {
delegate.onSend = nil
sent()
}
@@ -34,7 +35,7 @@ struct GossipSyncManagerTests {
}
manager.scheduleInitialSyncToPeer(PeerID(str: "FFFFFFFFFFFFFFFF"), delaySeconds: 0.0)
try await sleep(0.002)
try await TestHelpers.waitFor({ delegate.lastPacket != nil }, timeout: TestConstants.shortTimeout)
}
let lastPacket = try #require(delegate.lastPacket, "Expected sync packet to be sent")
@@ -240,7 +241,7 @@ struct GossipSyncManagerTests {
let request = RequestSyncPacket(p: 4, m: 1, data: Data(), types: .fragment)
manager.handleRequestSync(from: peer, request: request)
try await sleep(0.01)
try await TestHelpers.waitFor({ delegate.packets.count == 1 }, timeout: TestConstants.shortTimeout)
let sentPackets = delegate.packets
#expect(sentPackets.count == 1)
#expect(sentPackets[0].type == MessageType.fragment.rawValue)
+2 -2
View File
@@ -746,8 +746,8 @@ struct NoiseProtocolTests {
}
// Get transport ciphers
let (initSend, initRecv) = try initiatorHandshake.getTransportCiphers(useExtractedNonce: false)
let (respSend, respRecv) = try responderHandshake.getTransportCiphers(useExtractedNonce: false)
let (initSend, initRecv, _) = try initiatorHandshake.getTransportCiphers(useExtractedNonce: false)
let (respSend, respRecv, _) = try responderHandshake.getTransportCiphers(useExtractedNonce: false)
// Test transport messages (messages after the 3 handshake messages)
for index in 3..<testVector.messages.count {