Cleanup: remove dead code, normalize fingerprints, modernize share extension, trim test noise, and drop ‘preparing to share…’ message (#520)

* Remove dead code and artifacts: drop PeerManager, unused views/types; delete LegacyTestProtocolTypes; update .gitignore; purge TestResult.xcresult and build.log

* Tests: gate verbose prints under DEBUG; ChatViewModel: remove legacy fingerprint helper and rely on UnifiedPeerService

* Share Extension: migrate to UIKit + UTTypes; drop Social/SLComposeServiceViewController

* Remove 'preparing to share …' system message; send shared content immediately

* Inline comment cleanup: drop legacy 'removed' breadcrumbs across protocols, services, view model, and views

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-08-25 18:01:19 +02:00
committed by GitHub
co-authored by jack
parent 2f7c0aaaf7
commit 60b0deee7b
14 changed files with 166 additions and 532 deletions
+4
View File
@@ -71,3 +71,7 @@ __pycache__/
# Local build results
.Result*/
.Result*.xcresult/
TestResult.xcresult/
*.xcresult/
build.log
*.log
+1 -13
View File
@@ -109,20 +109,8 @@ struct BitchatApp: App {
userDefaults.removeObject(forKey: "sharedContentDate")
userDefaults.synchronize()
// Show notification about shared content
// Send the shared content immediately on the main queue
DispatchQueue.main.async {
// Add system message about sharing
let systemMessage = BitchatMessage(
sender: "system",
content: "preparing to share \(contentType)...",
timestamp: Date(),
isRelay: false
)
self.chatViewModel.messages.append(systemMessage)
}
// Send the shared content after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
if contentType == "url" {
// Try to parse as JSON first
if let data = sharedContent.data(using: .utf8),
+2 -51
View File
@@ -173,56 +173,7 @@ struct PendingActions {
var setPetname: String?
}
// MARK: - Privacy Settings
struct PrivacySettings: Codable {
// Level 1: Maximum privacy (default)
var persistIdentityCache = false
var showLastSeen = false
// Level 2: Convenience
var autoAcceptKnownFingerprints = false
var rememberNicknameHistory = false
// Level 3: Social
var shareTrustNetworkHints = false // "3 mutual contacts trust this person"
}
// MARK: - Conflict Resolution
/// Strategies for resolving identity conflicts in the decentralized network.
/// Handles cases where multiple peers claim the same nickname or when
/// identity mappings become ambiguous due to network partitions.
enum ConflictResolution {
case acceptNew(petname: String) // "John (2)"
case rejectNew
case blockFingerprint(String)
case alertUser(message: String)
}
// MARK: - UI State
struct PeerUIState {
let peerID: String
let nickname: String
var identityState: IdentityState
var connectionQuality: ConnectionQuality
enum IdentityState {
case unknown // Gray - No identity info
case unverifiedKnown(String) // Blue - Handshake done, matches cache
case verified(String) // Green - Cryptographically verified
case conflict(String, String) // Red - Nickname doesn't match fingerprint
case pending // Yellow - Handshake in progress
}
}
enum ConnectionQuality {
case excellent
case good
case poor
case disconnected
}
//
// MARK: - Migration Support
// Removed LegacyFavorite - no longer needed
//
+1 -216
View File
@@ -89,219 +89,4 @@ struct BitchatPeer: Identifiable, Equatable {
}
}
// MARK: - Peer Manager
/// Manages the collection of peers and their states
@MainActor
class PeerManager: ObservableObject {
@Published var peers: [BitchatPeer] = []
@Published var favorites: [BitchatPeer] = []
@Published var mutualFavorites: [BitchatPeer] = []
private let meshService: Transport
private let favoritesService = FavoritesPersistenceService.shared
init(meshService: Transport) {
self.meshService = meshService
updatePeers()
// Listen for updates
NotificationCenter.default.addObserver(
self,
selector: #selector(handleFavoriteChanged),
name: .favoriteStatusChanged,
object: nil
)
}
@objc private func handleFavoriteChanged() {
SecureLogger.log("⭐ Favorite status changed notification received, updating peers",
category: SecureLogger.session, level: .debug)
updatePeers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func updatePeers() {
// Reduce log verbosity - only log when count changes
let previousCount = peers.count
// Get current mesh peers
let meshPeers = meshService.getPeerNicknames()
// Build peer list
var allPeers: [BitchatPeer] = []
var connectedNicknames: Set<String> = []
var addedPeerIDs: Set<String> = []
// Add connected mesh peers (only if actually connected or relay connected)
for (peerID, nickname) in meshPeers {
guard let noiseKey = Data(hexString: peerID) else { continue }
// Safety check: Never add our own peer ID
if peerID == meshService.myPeerID {
continue
}
// Check if this peer is actually connected
let isConnected = meshService.isPeerConnected(peerID)
// Skip disconnected peers unless they're favorites (handled later)
if !isConnected {
continue
}
if isConnected {
connectedNicknames.insert(nickname)
}
// Track that we've added this peer ID
addedPeerIDs.insert(peerID)
var peer = BitchatPeer(
id: peerID,
noisePublicKey: noiseKey,
nickname: nickname,
isConnected: isConnected
)
// Set favorite status - check both by current noise key and by nickname
if let favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey) {
peer.favoriteStatus = favoriteStatus
peer.nostrPublicKey = favoriteStatus.peerNostrPublicKey
} else {
// Check if we have a favorite for this nickname (peer may have reconnected with new ID)
let favoriteByNickname = favoritesService.favorites.values.first { $0.peerNickname == nickname }
if let favorite = favoriteByNickname {
SecureLogger.log("🔄 Found favorite for '\(nickname)' by nickname, updating noise key",
category: SecureLogger.session, level: .info)
// Update the favorite's noise key to match the current connection
favoritesService.updateNoisePublicKey(from: favorite.peerNoisePublicKey, to: noiseKey, peerNickname: nickname)
// Get the updated favorite with the new key
peer.favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey)
peer.nostrPublicKey = peer.favoriteStatus?.peerNostrPublicKey ?? favorite.peerNostrPublicKey
}
}
allPeers.append(peer)
}
// Add offline favorites (only those not currently connected AND that we actively favorite)
for (favoriteKey, favorite) in favoritesService.favorites {
let favoriteID = favorite.peerNoisePublicKey.hexEncodedString()
// Skip if this peer is already connected (by nickname)
if connectedNicknames.contains(favorite.peerNickname) {
// Skipping favorite - already connected
continue
}
// Skip if we already added a peer with this ID (prevents duplicates)
if addedPeerIDs.contains(favoriteID) {
// Skipping favorite - peer ID already added
continue
}
// Only add peers that WE favorite (not just ones who favorite us)
if !favorite.isFavorite {
// Skipping - we don't favorite them
continue
}
// Add this favorite as an offline peer
SecureLogger.log(" - Adding offline favorite '\(favorite.peerNickname)' (key: \(favoriteKey.hexEncodedString()), ID: \(favoriteID), mutual: \(favorite.isMutual))",
category: SecureLogger.session, level: .info)
var peer = BitchatPeer(
id: favoriteID,
noisePublicKey: favorite.peerNoisePublicKey,
nickname: favorite.peerNickname,
isConnected: false
)
// Set favorite status
peer.favoriteStatus = favorite
peer.nostrPublicKey = favorite.peerNostrPublicKey
addedPeerIDs.insert(favoriteID) // Track that we've added this ID
allPeers.append(peer)
}
// Filter out "Unknown" peers unless they are favorites or have a favorite relationship
allPeers = allPeers.filter { peer in
!(peer.displayName == "Unknown" && peer.favoriteStatus == nil)
}
// Sort: Connected first, then favorites, then alphabetical
allPeers.sort { lhs, rhs in
// Direct connections first
if lhs.isConnected != rhs.isConnected {
return lhs.isConnected
}
// Then favorites
if lhs.isFavorite != rhs.isFavorite {
return lhs.isFavorite
}
// Finally alphabetical
return lhs.displayName < rhs.displayName
}
// Single pass to compute all subsets and counts
var favorites: [BitchatPeer] = []
var mutualFavorites: [BitchatPeer] = []
var connectedCount = 0
var offlineCount = 0
for peer in allPeers {
if peer.isFavorite {
favorites.append(peer)
}
if peer.isMutualFavorite {
mutualFavorites.append(peer)
}
if peer.isConnected {
connectedCount += 1
} else {
offlineCount += 1
}
}
// Final safety check: ensure no duplicate IDs
var finalPeers: [BitchatPeer] = []
var seenIDs: Set<String> = []
for peer in allPeers {
if !seenIDs.contains(peer.id) {
seenIDs.insert(peer.id)
finalPeers.append(peer)
} else {
SecureLogger.log("⚠️ Removing duplicate peer ID in final check: \(peer.id) (\(peer.displayName))",
category: SecureLogger.session, level: .warning)
}
}
self.peers = finalPeers
self.favorites = favorites
self.mutualFavorites = mutualFavorites
// Log peer list summary sparingly at debug level
if favoritesService.favorites.count > 0 {
SecureLogger.log("📊 Peer list update: \(allPeers.count) total (\(connectedCount) connected, \(offlineCount) offline), \(favorites.count) favorites, \(mutualFavorites.count) mutual",
category: SecureLogger.session, level: .debug)
} else if previousCount != allPeers.count {
SecureLogger.log("✅ Updated peer list: \(allPeers.count) total peers",
category: SecureLogger.session, level: .debug)
}
}
func toggleFavorite(_ peer: BitchatPeer) {
if peer.isFavorite {
favoritesService.removeFavorite(peerNoisePublicKey: peer.noisePublicKey)
} else {
favoritesService.addFavorite(
peerNoisePublicKey: peer.noisePublicKey,
peerNostrPublicKey: peer.nostrPublicKey,
peerNickname: peer.nickname
)
}
updatePeers()
}
}
//
+1 -1
View File
@@ -416,7 +416,7 @@ struct NostrProtocol {
// Log with explicit UTC and local time for debugging
let formatter = DateFormatter()
// Removed unnecessary date formatting operations
//
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(abbreviation: "UTC")
+3 -5
View File
@@ -181,8 +181,7 @@ enum LazyHandshakeState {
case failed(Error) // Handshake failed
}
// MARK: - Special Recipients (removed)
// Previously defined broadcast identifiers were unused; removed for simplicity.
//
// MARK: - Core Protocol Structures
@@ -268,8 +267,7 @@ struct BitchatPacket: Codable {
}
}
// MARK: - Delivery Acknowledgments (removed)
// Legacy DeliveryAck structures are no longer used; delivery status flows via Noise payloads.
//
// MARK: - Read Receipts
@@ -361,7 +359,7 @@ struct ReadReceipt: Codable {
}
// PeerIdentityBinding removed (unused).
//
// MARK: - Delivery Status
+1 -1
View File
@@ -730,7 +730,7 @@ final class BLEService: NSObject {
}
}
// Removed unused getPeers(): use getPeerNicknames() from Transport
//
// MARK: - Private Message Handling
+1 -1
View File
@@ -63,7 +63,7 @@ class CommandProcessor {
case "/unfav":
if inGeoPublic || inGeoDM { return .error(message: "favorites are only for mesh peers in #mesh") }
return handleFavorite(args, add: false)
// /help removed
//
case "/help", "/h":
return .error(message: "unknown command: \(cmd)")
default:
+9 -27
View File
@@ -297,7 +297,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}
}
// Missing properties that were removed during refactoring
//
private var peerIDToPublicKeyFingerprint: [String: String] = [:]
private var selectedPrivateChatFingerprint: String? = nil
// Map stable short peer IDs (16-hex) to full Noise public key hex (64-hex) for session continuity
@@ -1561,7 +1561,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
} catch {
// ignore
}
// Presence announcement removed; we will tag actual chat events instead
//
}
// MARK: - Geohash Participants
@@ -1714,7 +1714,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
let toAdd = desired.subtracting(current)
let toRemove = current.subtracting(desired)
// Unsubscribe removed
//
for (subID, gh) in geoSamplingSubs where toRemove.contains(gh) {
NostrRelayManager.shared.unsubscribe(id: subID)
geoSamplingSubs.removeValue(forKey: subID)
@@ -3617,26 +3617,8 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
return unifiedPeerService.getFingerprint(for: peerID)
}
private func getFingerprint_old(for peerID: String) -> String? {
// Remove debug logging to prevent console spam during view updates
// First try to get fingerprint from mesh service's peer ID rotation mapping
if let fingerprint = meshService.getFingerprint(for: peerID) {
return fingerprint
}
// Check noise service (direct Noise session fingerprint)
if let fingerprint = meshService.getNoiseService().getPeerFingerprint(peerID) {
return fingerprint
}
// Last resort: check local mapping
if let fingerprint = peerIDToPublicKeyFingerprint[peerID] {
return fingerprint
}
return nil
}
//
// Helper to resolve nickname for a peer ID through various sources
@MainActor
@@ -4034,7 +4016,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
messageRouter.flushOutbox(for: peerID)
}
// Connection messages removed to reduce chat noise
//
}
func didDisconnectFromPeer(_ peerID: String) {
@@ -4101,7 +4083,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}
}
// Disconnection messages removed to reduce chat noise
//
}
func didUpdatePeerList(_ peers: [String]) {
@@ -4414,7 +4396,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// MARK: - Simplified Nostr Integration (Inlined from MessageRouter)
// Removed inlined Nostr send helpers in favor of MessageRouter
//
@MainActor
private func setupNostrMessageHandling() {
@@ -4745,7 +4727,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
return Data(base64Encoded: str)
}
// Removed local TLV decoder; using PrivateMessagePacket.decode from Protocols
//
@MainActor
private func handleFavoriteNotificationFromMesh(_ content: String, from peerID: String, senderNickname: String) {
+3 -29
View File
@@ -13,19 +13,9 @@ import UIKit
// MARK: - Supporting Types
// Pre-computed peer data for performance
struct PeerDisplayData: Identifiable {
let id: String
let displayName: String
let isFavorite: Bool
let isMe: Bool
let hasUnreadMessages: Bool
let encryptionStatus: EncryptionStatus
let connectionState: BitchatPeer.ConnectionState
let isMutualFavorite: Bool
}
//
// (Link previews removed; URLs are now clickable inline)
//
// MARK: - Main Content View
@@ -1382,23 +1372,7 @@ private struct PaymentChipView: View {
}
}
// Helper view for rendering message content (plain, no hashtag/mention formatting)
struct MessageContentView: View {
let message: BitchatMessage
let viewModel: ChatViewModel
let colorScheme: ColorScheme
let isMentioned: Bool
var body: some View {
Text(message.content)
.font(.system(size: 14, design: .monospaced))
.fontWeight(isMentioned ? .bold : .regular)
}
// MARK: - Helper Methods
// buildTextSegments removed: content is rendered plain.
}
//
// Delivery status indicator view
struct DeliveryStatusView: View {
+130 -130
View File
@@ -7,160 +7,160 @@
//
import UIKit
import Social
import UniformTypeIdentifiers
class ShareViewController: SLComposeServiceViewController {
/// Modern share extension using UIKit + UTTypes.
/// Avoids deprecated Social framework and SLComposeServiceViewController.
final class ShareViewController: UIViewController {
private let statusLabel: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.font = .systemFont(ofSize: 15, weight: .semibold)
l.textAlignment = .center
l.numberOfLines = 0
l.textColor = .label
return l
}()
override func viewDidLoad() {
super.viewDidLoad()
// Set placeholder text
placeholder = "Share to bitchat..."
// Set character limit (optional)
charactersRemaining = 500
view.backgroundColor = .systemBackground
view.addSubview(statusLabel)
NSLayoutConstraint.activate([
statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
statusLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
statusLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.layoutMarginsGuide.leadingAnchor),
statusLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.layoutMarginsGuide.trailingAnchor)
])
processShare()
}
override func isContentValid() -> Bool {
// Validate that we have text content or attachments
if let text = contentText, !text.isEmpty {
return true
}
// Check if we have attachments
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let attachments = item.attachments,
!attachments.isEmpty {
return true
}
return false
}
override func didSelectPost() {
guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
// MARK: - Processing
private func processShare() {
guard let ctx = self.extensionContext,
let item = ctx.inputItems.first as? NSExtensionItem else {
finishWithMessage("Nothing to share")
return
}
// Get the page title from the compose view or extension item
let pageTitle = self.contentText ?? extensionItem.attributedContentText?.string ?? extensionItem.attributedTitle?.string
var foundURL: URL? = nil
let group = DispatchGroup()
// IMPORTANT: Check if the NSExtensionItem itself has a URL
// Safari often provides the URL as an attributedString with a link
if let attributedText = extensionItem.attributedContentText {
let text = attributedText.string
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
if let firstMatch = matches?.first, let url = firstMatch.url {
foundURL = url
}
// Try content from attributed text first (Safari often passes URL here)
if let url = detectURL(in: item.attributedContentText?.string ?? "") {
saveAndFinish(url: url, title: item.attributedTitle?.string)
return
}
// Only check attachments if we haven't found a URL yet
if foundURL == nil {
for (_, itemProvider) in (extensionItem.attachments ?? []).enumerated() {
// Try multiple URL type identifiers that Safari might use
let urlTypes = [
UTType.url.identifier,
"public.url",
"public.file-url"
]
for urlType in urlTypes {
if itemProvider.hasItemConformingToTypeIdentifier(urlType) {
group.enter()
itemProvider.loadItem(forTypeIdentifier: urlType, options: nil) { (item, error) in
defer { group.leave() }
if let url = item as? URL {
foundURL = url
} else if let data = item as? Data,
let urlString = String(data: data, encoding: .utf8),
let url = URL(string: urlString) {
foundURL = url
} else if let string = item as? String,
let url = URL(string: string) {
foundURL = url
}
}
break // Found a URL type, no need to check other types
}
// Scan attachments for URL/text
let providers = item.attachments ?? []
if providers.isEmpty {
// Fallback: use attributed title as plain text
if let title = item.attributedTitle?.string, !title.isEmpty {
saveAndFinish(text: title)
} else {
finishWithMessage("No shareable content")
}
// Also check for plain text that might be a URL
if foundURL == nil && itemProvider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
group.enter()
itemProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { (item, error) in
defer { group.leave() }
if let text = item as? String {
// Check if the text is actually a URL
if let url = URL(string: text),
(url.scheme == "http" || url.scheme == "https") {
foundURL = url
return
}
// Load URL or text asynchronously
loadFirstURL(from: providers) { [weak self] url in
guard let self = self else { return }
if let url = url {
self.saveAndFinish(url: url, title: item.attributedTitle?.string)
} else {
self.loadFirstPlainText(from: providers) { text in
if let t = text, !t.isEmpty {
// Treat as URL if parseable http(s), else plain text
if let u = URL(string: t), ["http","https"].contains(u.scheme?.lowercased() ?? "") {
self.saveAndFinish(url: u, title: item.attributedTitle?.string)
} else {
self.saveAndFinish(text: t)
}
} else {
self.finishWithMessage("No shareable content")
}
}
}
}
} // End of if foundURL == nil
// Process after all checks complete
group.notify(queue: .main) { [weak self] in
if let url = foundURL {
// We have a URL! Create the JSON data
let urlData: [String: String] = [
"url": url.absoluteString,
"title": pageTitle ?? url.host ?? "Shared Link"
]
if let jsonData = try? JSONSerialization.data(withJSONObject: urlData),
let jsonString = String(data: jsonData, encoding: .utf8) {
self?.saveToSharedDefaults(content: jsonString, type: "url")
}
private func detectURL(in text: String) -> URL? {
guard !text.isEmpty else { return nil }
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let range = NSRange(location: 0, length: (text as NSString).length)
let match = detector?.matches(in: text, options: [], range: range).first
return match?.url
}
private func loadFirstURL(from providers: [NSItemProvider], completion: @escaping (URL?) -> Void) {
let identifiers = [UTType.url.identifier, "public.url", "public.file-url"]
let grp = DispatchGroup()
var found: URL?
for p in providers where found == nil {
for id in identifiers where p.hasItemConformingToTypeIdentifier(id) {
grp.enter()
p.loadItem(forTypeIdentifier: id, options: nil) { item, _ in
defer { grp.leave() }
if let u = item as? URL { found = u; return }
if let s = item as? String, let u = URL(string: s) { found = u; return }
if let d = item as? Data, let s = String(data: d, encoding: .utf8), let u = URL(string: s) { found = u; return }
}
} else if let title = pageTitle, !title.isEmpty {
// No URL found, just share the text
self?.saveToSharedDefaults(content: title, type: "text")
break
}
self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
grp.notify(queue: .main) { completion(found) }
}
private func loadFirstPlainText(from providers: [NSItemProvider], completion: @escaping (String?) -> Void) {
let id = UTType.plainText.identifier
let grp = DispatchGroup()
var text: String?
for p in providers where p.hasItemConformingToTypeIdentifier(id) {
grp.enter()
p.loadItem(forTypeIdentifier: id, options: nil) { item, _ in
defer { grp.leave() }
if let s = item as? String { text = s }
else if let d = item as? Data, let s = String(data: d, encoding: .utf8) { text = s }
}
break
}
grp.notify(queue: .main) { completion(text) }
}
// MARK: - Save + Finish
private func saveAndFinish(url: URL, title: String?) {
let payload: [String: String] = [
"url": url.absoluteString,
"title": title ?? url.host ?? "Shared Link"
]
if let json = try? JSONSerialization.data(withJSONObject: payload),
let s = String(data: json, encoding: .utf8) {
saveToSharedDefaults(content: s, type: "url")
finishWithMessage("✓ Shared link to bitchat")
} else {
finishWithMessage("Failed to encode link")
}
}
override func configurationItems() -> [Any]! {
// No configuration items needed
return []
private func saveAndFinish(text: String) {
saveToSharedDefaults(content: text, type: "text")
finishWithMessage("✓ Shared text to bitchat")
}
// MARK: - Helper Methods
private func saveToSharedDefaults(content: String, type: String) {
// Use app groups to share data between extension and main app
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
return
}
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else { return }
userDefaults.set(content, forKey: "sharedContent")
userDefaults.set(type, forKey: "sharedContentType")
userDefaults.set(Date(), forKey: "sharedContentDate")
userDefaults.synchronize()
// Force open the main app
self.openMainApp()
}
private func openMainApp() {
// Share extensions cannot directly open the containing app
// The app will check for shared content when it becomes active
// Show success feedback to user
DispatchQueue.main.async {
self.textView.text = "✓ Shared to bitchat"
self.textView.isEditable = false
private func finishWithMessage(_ msg: String) {
statusLabel.text = msg
// Complete shortly after showing status
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
}
}
+10
View File
@@ -21,8 +21,10 @@ final class NostrProtocolTests: XCTestCase {
let sender = try NostrIdentity.generate()
let recipient = try NostrIdentity.generate()
#if DEBUG
print("Sender pubkey: \(sender.publicKeyHex)")
print("Recipient pubkey: \(recipient.publicKeyHex)")
#endif
// Create a test message
let originalContent = "Hello from NIP-17 test!"
@@ -34,8 +36,10 @@ final class NostrProtocolTests: XCTestCase {
senderIdentity: sender
)
#if DEBUG
print("Gift wrap created with ID: \(giftWrap.id)")
print("Gift wrap pubkey: \(giftWrap.pubkey)")
#endif
// Decrypt the gift wrap
let (decryptedContent, senderPubkey, timestamp) = try NostrProtocol.decryptPrivateMessage(
@@ -52,7 +56,9 @@ final class NostrProtocolTests: XCTestCase {
let timeDiff = abs(messageDate.timeIntervalSinceNow)
XCTAssertLessThan(timeDiff, 60, "Message timestamp should be recent")
#if DEBUG
print("✅ Successfully decrypted message: '\(decryptedContent)' from \(senderPubkey) at \(messageDate)")
#endif
}
func testGiftWrapUsesUniqueEphemeralKeys() throws {
@@ -75,8 +81,10 @@ final class NostrProtocolTests: XCTestCase {
// Gift wrap pubkeys should be different (unique ephemeral keys)
XCTAssertNotEqual(message1.pubkey, message2.pubkey)
#if DEBUG
print("Message 1 gift wrap pubkey: \(message1.pubkey)")
print("Message 2 gift wrap pubkey: \(message2.pubkey)")
#endif
// Both should decrypt successfully
let (content1, _, _) = try NostrProtocol.decryptPrivateMessage(
@@ -109,7 +117,9 @@ final class NostrProtocolTests: XCTestCase {
giftWrap: giftWrap,
recipientIdentity: wrongRecipient
)) { error in
#if DEBUG
print("Expected error when decrypting with wrong key: \(error)")
#endif
}
}
@@ -1,45 +0,0 @@
//
// LegacyTestProtocolTypes.swift
// bitchatTests
//
// Minimal legacy protocol types used only by tests to simulate old flows.
// These are not part of production code anymore.
import Foundation
struct ProtocolNack {
let originalPacketID: String
let nackID: String
let senderID: String
let receiverID: String
let packetType: UInt8
let reason: String
let errorCode: UInt8
enum ErrorCode: UInt8 {
case unknown = 0
case decryptionFailed = 2
}
init(originalPacketID: String, senderID: String, receiverID: String, packetType: UInt8, reason: String, errorCode: ErrorCode = .unknown) {
self.originalPacketID = originalPacketID
self.nackID = UUID().uuidString
self.senderID = senderID
self.receiverID = receiverID
self.packetType = packetType
self.reason = reason
self.errorCode = errorCode.rawValue
}
func toBinaryData() -> Data {
// Tests don't parse the payload; return a compact encoding for completeness
var data = Data()
data.appendUUID(originalPacketID)
data.appendUUID(nackID)
data.append(UInt8(packetType))
data.append(UInt8(errorCode))
data.appendString(reason)
return data
}
}
-13
View File
@@ -1,13 +0,0 @@
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -project bitchat.xcodeproj -scheme bitchat -configuration Debug -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO ONLY_ACTIVE_ARCH=YES
Build settings from command line:
CODE_SIGNING_ALLOWED = NO
ONLY_ACTIVE_ARCH = YES
SDKROOT = iphonesimulator18.5
Resolve Package Graph
/Users/jack/Library/org.swift.swiftpm/configuration is not accessible or not writable, disabling user-level cache features./Users/jack/Library/org.swift.swiftpm/security is not accessible or not writable, disabling user-level cache features./Users/jack/Library/Caches/org.swift.swiftpm is not accessible or not writable, disabling user-level cache features.
Package: swift-secp256k1
fatalError