diff --git a/.github/workflows/periphery.yml b/.github/workflows/periphery.yml new file mode 100644 index 00000000..e33fc7c2 --- /dev/null +++ b/.github/workflows/periphery.yml @@ -0,0 +1,30 @@ +name: Dead Code + +on: + push: + branches: + - main + pull_request: + +jobs: + periphery: + name: Periphery scan + runs-on: macos-latest + timeout-minutes: 30 + # Advisory, like SwiftLint (#1361): findings annotate the PR but don't + # block merges. Drop continue-on-error once the baseline proves stable. + continue-on-error: true + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install Periphery + # homebrew-core formula; the peripheryapp tap lags years behind. + run: brew install periphery + + - name: Scan for dead code + # Config comes from .periphery.yml; known findings (mostly iOS-only + # code invisible to a macOS scan) are suppressed by the committed + # baseline. --strict fails the step when NEW dead code appears. + run: periphery scan --strict --disable-update-check diff --git a/.periphery.baseline.json b/.periphery.baseline.json new file mode 100644 index 00000000..0b826869 --- /dev/null +++ b/.periphery.baseline.json @@ -0,0 +1 @@ +{"v1":{"usrs":["param-buf-arti_bootstrap_summary(_:_:)-s:3Tor22arti_bootstrap_summary33_954FD7701B4E47ABB5F166D1CF862DC9LLys5Int32VSpys4Int8VG_AEtF","param-dataDir-arti_start(_:_:)-s:3Tor10arti_start33_954FD7701B4E47ABB5F166D1CF862DC9LLys5Int32VSPys4Int8VG_s6UInt16VtF","param-len-arti_bootstrap_summary(_:_:)-s:3Tor22arti_bootstrap_summary33_954FD7701B4E47ABB5F166D1CF862DC9LLys5Int32VSpys4Int8VG_AEtF","param-socksPort-arti_start(_:_:)-s:3Tor10arti_start33_954FD7701B4E47ABB5F166D1CF862DC9LLys5Int32VSPys4Int8VG_s6UInt16VtF","s:13BitFoundation16PeerCapabilitiesV8wifiBulkACvpZ","s:13BitFoundation18KeychainReadResultO18isRecoverableErrorSbvp","s:13BitFoundation23KeychainManagerProtocolP11secureClearyySSzF","s:18bitchatTests_macOS12MockKeychainC11secureClearyySSzF","s:18bitchatTests_macOS20TrackingMockKeychainC11resetCountsyyF","s:18bitchatTests_macOS20TrackingMockKeychainC11secureClearyySSzF","s:18bitchatTests_macOS20TrackingMockKeychainC25totalSecureClearCallCountSivp","s:18bitchatTests_macOS20TrackingMockKeychainC26secureClearStringCallCountSivp","s:18bitchatTests_macOS20TrackingMockKeychainC27_secureClearStringCallCount06_AB6D1M24FD239F2969C82F4108818260LLSivp","s:18bitchatTests_macOS24FailingCacheSaveKeychain33_22380C7A11A569A0B83FA83F34C498A7LLC11secureClearyySSzF","s:18bitchatTests_macOS24MockGeohashPresenceTimer33_483587EFB96650EE130EFB09BBA2A1AALLC7handleryycvp","s:3Tor0A7ManagerC21goDormantOnBackgroundyyF","s:7bitchat10AppRuntimeC24handleScreenshotCaptured33_C8B369AD8BC1D9963A50CEDA77A4332ALLyyF","s:7bitchat10AppRuntimeC33handleDidBecomeActiveNotificationyyF","s:7bitchat10BLEServiceC18logBluetoothStatus33_69191C53E68500C17D98DBCF2BDA7100LLyySSF","s:7bitchat10BLEServiceC20centralRestorationID33_69191C53E68500C17D98DBCF2BDA7100LLSSvpZ","s:7bitchat10BLEServiceC22captureBluetoothStatus33_69191C53E68500C17D98DBCF2BDA7100LL7contextySS_tF","s:7bitchat10BLEServiceC23peripheralRestorationID33_69191C53E68500C17D98DBCF2BDA7100LLSSvpZ","s:7bitchat10BLEServiceC29scheduleBluetoothStatusSample33_69191C53E68500C17D98DBCF2BDA7100LL5after7contextySd_SStF","s:7bitchat10QRScanViewV8isActiveSbvp","s:7bitchat15BLEPeerRegistryV5countSivp","s:7bitchat15KeychainManagerC11secureClearyySSzF","s:7bitchat15PaymentChipViewV7openURL33_10AC50641B1EBCD52E5092A2E521D236LL7SwiftUI13OpenURLActionVvp","s:7bitchat15TransportConfigO29uiBatchDispatchStaggerSecondsSdvpZ","s:7bitchat15TransportConfigO35uiShareExtensionDismissDelaySecondsSdvpZ","s:7bitchat15TransportConfigO38bleBackgroundPendingConnectSlotReserveSivpZ","s:7bitchat17GossipSyncManagerC10persistNowyyF","s:7bitchat17NostrRelayManagerC15InboundEventKey33_E4160FE8A9A2C9D6308EAAD5A8B5CB07LLV7eventIDSSvp","s:7bitchat25LocationNotesDependenciesV3now10Foundation4DateVycvp","s:7bitchat25NWPathReachabilityMonitorC7monitor33_84633C9DBCAF57538179C1E04DB8E015LL7Network0bD0CSgvp"]}} \ No newline at end of file diff --git a/.periphery.yml b/.periphery.yml new file mode 100644 index 00000000..865def40 --- /dev/null +++ b/.periphery.yml @@ -0,0 +1,14 @@ +# Periphery dead-code scan configuration (https://github.com/peripheryapp/periphery) +# +# CI runs the macOS scheme only (an iOS scan needs a device destination and +# doubles the build time). macOS-only scans falsely flag iOS-only code — +# state restoration, screenshot handlers, background BLE sampling — so those +# findings live in .periphery.baseline.json rather than being "fixed". +# When auditing by hand, scan BOTH schemes and intersect: +# periphery scan --schemes "bitchat (iOS)" -- -destination 'generic/platform=iOS' ARCHS=arm64 +project: bitchat.xcodeproj +schemes: + - bitchat (macOS) +retain_swift_ui_previews: true +relative_results: true +baseline: .periphery.baseline.json diff --git a/bitchat/App/AppArchitecture.swift b/bitchat/App/AppArchitecture.swift index a0710673..72b43b7d 100644 --- a/bitchat/App/AppArchitecture.swift +++ b/bitchat/App/AppArchitecture.swift @@ -36,34 +36,12 @@ enum AppEvent: Sendable, Equatable { actor AppEventStream { private var continuations: [UUID: AsyncStream.Continuation] = [:] - func stream() -> AsyncStream { - let id = UUID() - return AsyncStream { continuation in - continuations[id] = continuation - continuation.onTermination = { [id] _ in - Task { - await self.removeContinuation(id) - } - } - } - } - func emit(_ event: AppEvent) { for continuation in continuations.values { continuation.yield(event) } } - func finish() { - for continuation in continuations.values { - continuation.finish() - } - continuations.removeAll() - } - - private func removeContinuation(_ id: UUID) { - continuations.removeValue(forKey: id) - } } /// Identity key for a direct conversation. Equality and hashing use the diff --git a/bitchat/App/AppRuntime.swift b/bitchat/App/AppRuntime.swift index de096e8e..27d0d993 100644 --- a/bitchat/App/AppRuntime.swift +++ b/bitchat/App/AppRuntime.swift @@ -18,8 +18,6 @@ final class AppRuntime: ObservableObject { /// (docs/CONVERSATION-STORE-DESIGN.md). Owned here; the feature models /// and `ChatViewModel` observe and mutate it through its intent API. let conversations: ConversationStore - let peerIdentityStore: PeerIdentityStore - let locationPresenceStore: LocationPresenceStore let publicChatModel: PublicChatModel let privateInboxModel: PrivateInboxModel let privateConversationModel: PrivateConversationModel @@ -51,8 +49,6 @@ final class AppRuntime: ObservableObject { let locationPresenceStore = LocationPresenceStore() let locationManager = LocationChannelManager.shared self.conversations = conversations - self.peerIdentityStore = peerIdentityStore - self.locationPresenceStore = locationPresenceStore self.chatViewModel = ChatViewModel( keychain: keychain, idBridge: idBridge, diff --git a/bitchat/App/ConversationStore.swift b/bitchat/App/ConversationStore.swift index 88d72085..c5195ae3 100644 --- a/bitchat/App/ConversationStore.swift +++ b/bitchat/App/ConversationStore.swift @@ -796,16 +796,6 @@ extension ConversationStore { return messageIDs } - /// Removes every direct conversation (panic clear). - func removeAllDirectConversations() { - let directIDs = conversationIDs.filter { id in - if case .direct = id { return true } - return false - } - for id in directIDs { - removeConversation(id) - } - } } // MARK: - Diagnostics support diff --git a/bitchat/App/LocationChannelsModel.swift b/bitchat/App/LocationChannelsModel.swift index d7431d6d..230e75a0 100644 --- a/bitchat/App/LocationChannelsModel.swift +++ b/bitchat/App/LocationChannelsModel.swift @@ -1,4 +1,3 @@ -import BitFoundation import Combine import Foundation @@ -17,7 +16,6 @@ final class LocationChannelsModel: ObservableObject { private let manager: LocationChannelManager private let network: NetworkActivationService private let gateway: GatewayService - private var cancellables = Set() init( manager: LocationChannelManager? = nil, diff --git a/bitchat/App/PeerIdentityStore.swift b/bitchat/App/PeerIdentityStore.swift index 119d765f..e7e49f01 100644 --- a/bitchat/App/PeerIdentityStore.swift +++ b/bitchat/App/PeerIdentityStore.swift @@ -25,10 +25,6 @@ final class PeerIdentityStore: ObservableObject { stablePeerIDsByShortID[peerID] = stablePeerID } - func replaceStablePeerIDs(_ mappings: [PeerID: PeerID]) { - stablePeerIDsByShortID = mappings - } - func fingerprint(for peerID: PeerID) -> String? { peerFingerprintsByPeerID[peerID] } @@ -94,10 +90,6 @@ final class PeerIdentityStore: ObservableObject { invalidateEncryptionCache(for: peerID) } - func replaceEncryptionStatuses(_ statuses: [PeerID: EncryptionStatus]) { - encryptionStatuses = statuses - } - func setVerifiedFingerprints(_ fingerprints: Set) { verifiedFingerprints = fingerprints } diff --git a/bitchat/App/VerificationModel.swift b/bitchat/App/VerificationModel.swift index 5916a958..d9c2ce08 100644 --- a/bitchat/App/VerificationModel.swift +++ b/bitchat/App/VerificationModel.swift @@ -3,7 +3,6 @@ import Combine import Foundation struct FingerprintPresentationState: Equatable { - let statusPeerID: PeerID let peerNickname: String let encryptionStatus: EncryptionStatus let theirFingerprint: String? @@ -56,10 +55,6 @@ final class VerificationModel: ObservableObject { return VerificationService.shared.buildMyQRString(nickname: currentNickname, npub: npub) ?? "" } - func beginQRVerification(with qr: VerificationService.VerificationQR) -> Bool { - chatViewModel.beginQRVerification(with: qr) - } - func verifyScannedPayload(_ payload: String) -> VerificationScanOutcome { guard let qr = VerificationService.shared.verifyScannedQR(payload) else { return .invalid @@ -110,7 +105,6 @@ final class VerificationModel: ObservableObject { } return FingerprintPresentationState( - statusPeerID: statusPeerID, peerNickname: peerNickname, encryptionStatus: encryptionStatus, theirFingerprint: theirFingerprint, diff --git a/bitchat/Features/voice/VoiceRecorder.swift b/bitchat/Features/voice/VoiceRecorder.swift index b7b4b91f..586e8fdf 100644 --- a/bitchat/Features/voice/VoiceRecorder.swift +++ b/bitchat/Features/voice/VoiceRecorder.swift @@ -5,7 +5,6 @@ import AVFoundation actor VoiceRecorder { enum RecorderError: Error { case microphoneAccessDenied - case recorderInitializationFailed case recordingInProgress } diff --git a/bitchat/Features/voice/Waveform.swift b/bitchat/Features/voice/Waveform.swift index e33c72bd..cfc79310 100644 --- a/bitchat/Features/voice/Waveform.swift +++ b/bitchat/Features/voice/Waveform.swift @@ -56,12 +56,6 @@ final class WaveformCache { } } - func purgeAll() { - queue.async(flags: .barrier) { [weak self] in - self?.cache.removeAll() - } - } - private func computeWaveform(url: URL, bins: Int) -> [Float]? { guard bins > 0 else { return nil } // Use autoreleasepool to manage memory from audio buffer allocations diff --git a/bitchat/Identity/IdentityModels.swift b/bitchat/Identity/IdentityModels.swift index 921a071a..0d8dc475 100644 --- a/bitchat/Identity/IdentityModels.swift +++ b/bitchat/Identity/IdentityModels.swift @@ -88,8 +88,6 @@ import BitFoundation /// Represents the ephemeral layer of identity - short-lived peer IDs that provide network privacy. /// These IDs rotate periodically to prevent tracking while maintaining cryptographic relationships. struct EphemeralIdentity { - let peerID: PeerID // 8 random bytes - let sessionStart: Date var handshakeState: HandshakeState } @@ -98,7 +96,6 @@ enum HandshakeState { case initiated case inProgress case completed(fingerprint: String) - case failed(reason: String) } /// Represents the cryptographic layer of identity - the stable Noise Protocol static key pair. @@ -110,7 +107,6 @@ struct CryptographicIdentity: Codable { // Optional Ed25519 signing public key (used to authenticate public messages) var signingPublicKey: Data? = nil let firstSeen: Date - let lastHandshake: Date? } /// Represents the social layer of identity - user-assigned names and trust relationships. @@ -193,9 +189,6 @@ struct IdentityCache: Codable { // Fingerprint -> when we verified it (orders outgoing vouch batches; // entries verified before this field exists sort as oldest) var verifiedAt: [String: Date]? = nil - - // Schema version for future migrations - var version: Int = 1 } // diff --git a/bitchat/Identity/SecureIdentityStateManager.swift b/bitchat/Identity/SecureIdentityStateManager.swift index 021ea776..7c68a01b 100644 --- a/bitchat/Identity/SecureIdentityStateManager.swift +++ b/bitchat/Identity/SecureIdentityStateManager.swift @@ -108,8 +108,6 @@ protocol SecureIdentityStateManagerProtocol { func updateSocialIdentity(_ identity: SocialIdentity) // MARK: Favorites Management - func getFavorites() -> Set - func setFavorite(_ fingerprint: String, isFavorite: Bool) func isFavorite(fingerprint: String) -> Bool // MARK: Blocked Users Management @@ -123,8 +121,7 @@ protocol SecureIdentityStateManagerProtocol { // MARK: Ephemeral Session Management func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) - func updateHandshakeState(peerID: PeerID, state: HandshakeState) - + // MARK: Cleanup func clearAllIdentityData() func removeEphemeralSession(peerID: PeerID) @@ -139,7 +136,6 @@ protocol SecureIdentityStateManagerProtocol { func recordVouch(voucheeFingerprint: String, voucherFingerprint: String, timestamp: Date) -> Bool func validVouchers(for fingerprint: String) -> [VouchRecord] func isVouched(fingerprint: String) -> Bool - func effectiveTrustLevel(for fingerprint: String) -> TrustLevel func lastVouchBatchSent(to fingerprint: String) -> Date? func markVouchBatchSent(to fingerprint: String, at date: Date) func signingPublicKey(forFingerprint fingerprint: String) -> Data? @@ -322,21 +318,13 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol { fingerprint: fingerprint, publicKey: noisePublicKey, signingPublicKey: signingPublicKey ?? existing.signingPublicKey, - firstSeen: existing.firstSeen, - lastHandshake: now + firstSeen: existing.firstSeen ) self.cryptographicIdentities[fingerprint] = existing } else { - // Update signing key and lastHandshake + // Update signing key existing.signingPublicKey = signingPublicKey ?? existing.signingPublicKey - let updated = CryptographicIdentity( - fingerprint: existing.fingerprint, - publicKey: existing.publicKey, - signingPublicKey: existing.signingPublicKey, - firstSeen: existing.firstSeen, - lastHandshake: now - ) - self.cryptographicIdentities[fingerprint] = updated + self.cryptographicIdentities[fingerprint] = existing } // Persist updated state (already assigned in branches above) } else { @@ -345,8 +333,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol { fingerprint: fingerprint, publicKey: noisePublicKey, signingPublicKey: signingPublicKey, - firstSeen: now, - lastHandshake: now + firstSeen: now ) self.cryptographicIdentities[fingerprint] = entry } @@ -511,11 +498,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol { func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState = .none) { queue.async(flags: .barrier) { - self.ephemeralSessions[peerID] = EphemeralIdentity( - peerID: peerID, - sessionStart: Date(), - handshakeState: handshakeState - ) + self.ephemeralSessions[peerID] = EphemeralIdentity(handshakeState: handshakeState) } } diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index 12e3f978..c0be8226 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -1082,185 +1082,6 @@ } } }, - "app_info.close" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "إغلاق" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "বন্ধ করুন" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "schließen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "close" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "cerrar" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "isara" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "fermer" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "סגור" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "बंद करें" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "tutup" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "chiudi" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "閉じる" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "닫기" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "tutup" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "बन्द" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "sluiten" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "zamknij" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "fechar" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "fechar" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "закрыть" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "stäng" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "மூடு" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ปิด" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "kapat" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "закрити" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "بند کریں" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "đóng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "关闭" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "關閉" - } - } - } - }, "app_info.done" : { "extractionState" : "manual", "localizations" : { @@ -10049,364 +9870,6 @@ } } }, - "app_info.warning.message" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "أمان الرسائل الخاصة لم يتم تدقيقه بالكامل بعد. لا تستخدمها في الحالات الحرجة حتى يختفي هذا التحذير." - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "ব্যক্তিগত বার্তার নিরাপত্তা এখনো সম্পূর্ণ অডিট হয়নি। এই সতর্কতা না থাকা পর্যন্ত জরুরি পরিস্থিতিতে ব্যবহার করবেন না।" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "die sicherheit privater nachrichten wurde noch nicht vollständig geprüft. nutze sie nicht für kritische situationen, solange dieser hinweis erscheint." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "private message security has not yet been fully audited. do not use for critical situations until this warning disappears." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "la seguridad de los mensajes privados aún no ha sido auditada por completo. no lo uses en situaciones críticas hasta que este aviso desaparezca." - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "hindi pa ganap na na-audit ang seguridad ng pribadong mensahe. huwag gamitin para sa kritikal na sitwasyon hangga't hindi nawawala ang babalang ito." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "la sécurité des messages privés n'a pas encore été entièrement auditée. n'utilise pas pour des situations critiques tant que cet avertissement reste." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אבטחת ההודעות הפרטיות עדיין לא נבדקה במלואה. אל תשתמש למצבים קריטיים עד שהאזהרה תיעלם." - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "निजी संदेश सुरक्षा का अभी पूरा ऑडिट नहीं हुआ है। यह चेतावनी हटने तक गंभीर स्थितियों में उपयोग न करें।" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "keamanan pesan pribadi belum diaudit sepenuhnya. jangan dipakai untuk situasi kritis sampai peringatan ini hilang." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "la sicurezza dei messaggi privati non è stata ancora auditata completamente. non usarli in situazioni critiche finché questo avviso resta." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "プライベートメッセージの安全性はまだ完全に監査されていません。この警告が消えるまで重要な場面では使わないでください。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "비공개 메시지 보안은 아직 완전히 감사받지 않았습니다. 이 경고가 사라질 때까지 중요한 상황에서는 사용하지 마세요." - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "keamanan pesan pribadi belum diaudit sepenuhnya. jangan diguna untuk situasi kritis sampai peringatan ini hilang." - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "व्यक्तिगत सन्देशको सुरक्षा पूर्ण रूपमा अडिट भएको छैन। यो चेतावनी हट्दासम्म गम्भीर अवस्थामा प्रयोग नगर्नु।" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "de beveiliging van privéberichten is nog niet volledig geaudit. gebruik dit niet in kritieke situaties totdat deze melding verdwijnt." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "bezpieczeństwo wiadomości prywatnych nie zostało jeszcze w pełni sprawdzone. nie używaj w sytuacjach krytycznych, dopóki to ostrzeżenie nie zniknie." - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "a segurança das mensagens privadas ainda não foi totalmente auditada. não uses em situações críticas até este aviso desaparecer." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "a segurança das mensagens privadas ainda não foi totalmente auditada. não use em situações críticas até que este aviso desapareça." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "безопасность приватных сообщений ещё не прошла полный аудит. не используй для критичных случаев, пока предупреждение не исчезнет." - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "säkerheten för privata meddelanden är ännu inte fullständigt granskad. använd inte i kritiska situationer förrän detta meddelande försvinner." - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "தனிப்பட்ட செய்தி பாதுகாப்பு இன்னும் முழுமையாக ஆய்வு செய்யப்படவில்லை. இந்த எச்சரிக்கை மறையும் வரை முக்கிய அவசரங்களுக்கு பயன்படுத்தாதீர்கள்." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ความปลอดภัยของข้อความส่วนตัวยังไม่ได้รับการตรวจสอบทั้งหมด อย่าใช้ในสถานการณ์วิกฤติจนกว่าคำเตือนนี้จะหายไป" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "özel mesaj güvenliği henüz tamamen denetlenmedi. bu uyarı kaybolana kadar kritik durumlarda kullanmayın." - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "безпека приватних повідомлень ще не пройшла повний аудит. не використовуй для критичних ситуацій, поки це попередження не зникне." - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "نجی پیغامات کی سیکیورٹی کا ابھی مکمل آڈٹ نہیں ہوا۔ اس انتباہ کے ختم ہونے تک اسے اہم حالات میں استعمال نہ کریں۔" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "bảo mật tin nhắn riêng tư vẫn chưa được kiểm toán đầy đủ. đừng dùng cho tình huống quan trọng cho tới khi cảnh báo này biến mất." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "私信安全尚未完全审计。在此警告消失前不要用于关键情境。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "私信安全尚未完全審計。在此警告消失前不要用於關鍵情境。" - } - } - } - }, - "app_info.warning.title" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "تحذير" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "সতর্কতা" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "WARNUNG" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "WARNING" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "ADVERTENCIA" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "BABALA" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "AVERTISSEMENT" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אזהרה" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "चेतावनी" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "PERINGATAN" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "AVVISO" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "警告" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "경고" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "PERINGATAN" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "चेतावनी" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "WAARSCHUWING" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "OSTRZEŻENIE" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "AVISO" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "AVISO" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "ПРЕДУПРЕЖДЕНИЕ" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "VARNING" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "எச்சரிக்கை" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "คำเตือน" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "UYARI" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "ПОПЕРЕДЖЕННЯ" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "انتباہ" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "CẢNH BÁO" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "警告" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "警告" - } - } - } - }, "Choose an image" : { "comment" : "A label displayed above a button that allows the user to choose an image to send.", "extractionState" : "manual", @@ -14712,185 +14175,6 @@ } } }, - "content.accessibility.location_notes" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "ملاحظات الموقع لهذا المكان" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "এই স্থানের লোকেশন নোট" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "standortnotizen für diesen ort" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "location notes for this place" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas de ubicación de este lugar" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "mga tala para sa lugar na ito" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "notes de localisation pour cet endroit" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הערות מיקום למקום הזה" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "इस स्थान के लोकेशन नोट" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "catatan lokasi untuk tempat ini" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "note di posizione per questo posto" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "この場所のロケーションノート" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이 장소의 위치 노트" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "catatan lokasi untuk tempat ini" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "यस ठाउँका स्थान नोटहरू" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "locatienotities voor deze plek" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "notatki lokalizacyjne dla tego miejsca" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas de localização deste lugar" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas de localização deste lugar" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "заметки для этого места" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "platsanteckningar för den här platsen" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "இந்த இடத்திற்கான குறிப்புகள்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "บันทึกตำแหน่งสำหรับสถานที่นี้" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "bu yer için konum notları" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "замітки про це місце" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "اس جگہ کیلئے لوکیشن نوٹس" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "ghi chú vị trí cho nơi này" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "此位置的笔记" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "此位置的筆記" - } - } - } - }, "content.accessibility.notices" : { "comment" : "Accessibility label for the notices button", "extractionState" : "manual", @@ -18505,185 +17789,6 @@ } } }, - "content.accessibility.toggle_favorite_hint" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "اضغط مرتين لتبديل حالة المفضلة" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "প্রিয় অবস্থা বদলাতে দুইবার ট্যাপ করুন" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "doppelt tippen, um favoritenstatus zu wechseln" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "double tap to toggle favorite status" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "toca dos veces para alternar el estado de favorito" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "i-double tap para i-toggle ang paborito" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "tape deux fois pour basculer le statut favori" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הקש פעמיים כדי להחליף מצב מועדפים" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "पसंदीदा स्थिति बदलने के लिए डबल टैप करें" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "ketuk dua kali untuk mengubah status favorit" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "tocca due volte per cambiare stato preferito" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ダブルタップでお気に入り状態を切り替え" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "두 번 탭하여 즐겨찾기 상태 토글" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "ketuk dua kali untuk mengubah status favorit" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "मनपर्ने स्थिति बदल्न दोहोरो ट्याप गर" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "dubbelklikken om favoriet te schakelen" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "stuknij dwukrotnie, aby zmienić stan ulubionych" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "toca duas vezes para alternar o estado de favorito" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "toque duas vezes para alternar status de favorito" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "дважды тапни, чтобы переключить статус избранного" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "dubbeltryck för att växla favoritstatus" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "பிரியப்பட்ட நிலையை மாற்ற இருமுறை தட்டவும்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "แตะสองครั้งเพื่อสลับสถานะรายการโปรด" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "favori durumunu değiştirmek için çift dokunun" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "торкни двічі, щоб змінити статус вибраного" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "پسندیدہ حالت بدلنے کیلئے دو بار ٹیپ کریں" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "chạm hai lần để chuyển trạng thái yêu thích" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "双击切换收藏状态" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "雙擊切換收藏狀態" - } - } - } - }, "content.accessibility.verification" : { "comment" : "Accessibility label for the verification QR button", "extractionState" : "manual", @@ -30702,185 +29807,6 @@ } } }, - "content.notes.title" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "ملاحظات" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "নোট" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "notizen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "notes" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "mga tala" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "notes" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הערות" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "नोट्स" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "catatan" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "note" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ノート" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "노트" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "catatan" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "नोट" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "notities" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "notatki" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "notas" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "заметки" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "anteckningar" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "குறிப்புகள்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "บันทึก" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "notlar" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "замітки" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "نوٹس" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "ghi chú" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "笔记" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "筆記" - } - } - } - }, "content.payment.cashu" : { "extractionState" : "manual", "localizations" : { @@ -45160,364 +44086,6 @@ } } }, - "location_notes.empty_subtitle" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "كن أول من يضيف هنا." - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "এই জায়গার জন্য প্রথম নোট যোগ করুন।" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "sei die erste person, die hier eine notiz hinterlässt." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "be the first to add one for this spot." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "sé la primera persona en añadir una en este lugar." - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "maging unang magdagdag para sa puntong ito" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "sois la première personne à en ajouter ici." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "היה הראשון להוסיף כאן." - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "इस जगह के लिए पहला नोट आप जोड़ें।" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "jadilah orang pertama yang menambahkannya di sini." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "fai tu la prima nota qui." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ここで最初のノートを残そう。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이 장소에 첫 번째 노트를 남겨보세요." - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "jadilah orang pertama yang menambahkannya di sini." - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "यस ठाउँमा नोट थप्ने पहिलो व्यक्ती बन।" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "wees de eerste die er hier een toevoegt" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "dodaj pierwszą notatkę dla tego miejsca" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "sê o primeiro a adicionar uma nota para este sítio." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "seja a primeira pessoa a adicionar uma aqui." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "стань первым, кто добавит здесь заметку." - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "var först med en anteckning här" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "இந்த இடத்திற்கு முதலில் ஒரு குறிப்பைச் சேர்க்கவும்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เป็นคนแรกที่เพิ่มบันทึกให้จุดนี้" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "bu yer için ilk notu sen ekle." - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "стань першим, хто додасть тут замітку." - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "اس مقام کیلئے پہلا نوٹ آپ شامل کریں" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "hãy là người đầu tiên thêm ghi chú tại đây" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "成为这里的第一条笔记。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "成為這裡的第一條筆記。" - } - } - } - }, - "location_notes.empty_title" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "لا توجد ملاحظات بعد" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "এখনও কোনো নোট নেই" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "noch keine notizen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "no notes yet" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "aún no hay notas" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "wala pang tala" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "pas encore de notes" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אין הערות עדיין" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "अभी कोई नोट नहीं" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "belum ada catatan" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "ancora nessuna nota" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ノートはまだありません" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "아직 노트가 없습니다" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "belum ada catatan" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "अहिले नोट छैन" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "nog geen notities" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "brak notatek" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "ainda sem notas" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "nenhuma nota ainda" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "заметок пока нет" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "inga anteckningar än" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "இன்னும் குறிப்புகள் இல்லை" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ยังไม่มีบันทึก" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "henüz not yok" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "заміток ще немає" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "ابھی تک کوئی نوٹس نہیں" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "chưa có ghi chú" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "尚无笔记" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "尚無筆記" - } - } - } - }, "location_notes.error.failed_to_send" : { "extractionState" : "manual", "localizations" : { @@ -45876,569 +44444,6 @@ } } }, - "location_notes.header" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "few" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظات" - } - }, - "many" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظة" - } - }, - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظة" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظة" - } - }, - "two" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظتان" - } - }, - "zero" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملاحظات" - } - } - } - } - } - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notiz" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notizen" - } - } - } - } - } - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d note" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notes" - } - } - } - } - } - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d nota" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notas" - } - } - } - } - } - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d note" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notes" - } - } - } - } - } - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "many" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d הערות" - } - }, - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d הערה" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d הערות" - } - }, - "two" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d הערות" - } - } - } - } - } - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d catatan" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d catatan" - } - } - } - } - } - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d nota" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d note" - } - } - } - } - } - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d件のノート" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d件のノート" - } - } - } - } - } - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d개의 노트" - } - } - } - } - } - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d नोट" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d नोटहरू" - } - } - } - } - } - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d nota" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d notas" - } - } - } - } - } - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "few" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d заметки" - } - }, - "many" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d заметок" - } - }, - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d заметка" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d заметки" - } - } - } - } - } - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "few" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d замітки" - } - }, - "many" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d заміток" - } - }, - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d замітка" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d замітки" - } - } - } - } - } - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - }, - "substitutions" : { - "note_count" : { - "argNum" : 2, - "formatSpecifier" : "lld", - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d 条笔记" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d 条笔记" - } - } - } - } - } - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "#%1$@ • %2$#@note_count@" - } - } - } - }, "location_notes.loading_notes" : { "extractionState" : "manual", "localizations" : { @@ -46618,185 +44623,6 @@ } } }, - "location_notes.loading_recent" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "جار تحميل الملاحظات الحديثة…" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "সাম্প্রতিক নোট লোড হচ্ছে…" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "aktuelle notizen werden geladen…" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "loading recent notes…" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "cargando notas recientes…" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "nagse-load ng pinakahuling mga tala…" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "chargement des notes récentes…" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "טוען הערות אחרונות…" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "हाल के नोट लोड हो रहे हैं…" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "memuat catatan terbaru…" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "caricamento note recenti…" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "最新ノートを読み込み中…" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "최근 노트 로딩 중…" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "memuat catatan terbaru…" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "हालैका नोट लोड गर्दै…" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "recentste notities laden…" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "ładowanie ostatnich notatek…" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "a carregar notas recentes…" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "carregando notas recentes…" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "загрузка свежих заметок…" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "laddar senaste anteckningar…" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "சமீபத்திய குறிப்புகள் ஏற்றப்படுகின்றன…" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "กำลังโหลดบันทึกล่าสุด…" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "son notlar yükleniyor…" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "завантаження свіжих заміток…" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "حالیہ نوٹس لوڈ ہو رہے ہیں…" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "đang tải ghi chú gần đây…" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "正在加载最新笔记…" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "正在加載最新筆記…" - } - } - } - }, "location_notes.no_relays_nearby" : { "extractionState" : "manual", "localizations" : { @@ -46976,364 +44802,6 @@ } } }, - "location_notes.placeholder" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "أضف ملاحظة لهذا المكان" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "এই স্থানের জন্য একটি নোট যোগ করুন" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "notiz für diesen ort hinzufügen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "add a note for this place" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "añade una nota para este lugar" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "magdagdag ng tala para sa lugar na ito" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "ajoute une note pour cet endroit" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הוסף הערה למקום הזה" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "इस स्थान के लिए नोट जोड़ें" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "tambahkan catatan untuk tempat ini" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "aggiungi una nota per questo posto" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "この場所のノートを追加" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이 장소에 대한 노트 추가" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "tambahkan catatan untuk tempat ini" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "यस स्थानका लागि नोट थप" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "voeg een notitie toe voor deze plek" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "dodaj notatkę do tego miejsca" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "adiciona uma nota para este local" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "adicione uma nota para este lugar" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "добавь заметку для этого места" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "lägg till en anteckning för den här platsen" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "இந்த இடத்திற்கான ஒரு குறிப்பைச் சேர்க்கவும்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เพิ่มบันทึกให้สถานที่นี้" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "bu yer için bir not ekleyin" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "додай замітку для цього місця" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "اس جگہ کیلئے نوٹ شامل کریں" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "thêm ghi chú cho nơi này" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "为此地点添加笔记" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "為此地點添加筆記" - } - } - } - }, - "location_notes.relays_paused" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "المرحلات الجغرافية غير متاحة؛ الملاحظات متوقفة" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "জিও রিলে অনুপলব্ধ; নোট স্থগিত" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo-relays nicht verfügbar; notizen pausiert" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo relays unavailable; notes paused" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "relays geográficos no disponibles; notas en pausa" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "hindi magagamit ang mga geo relay; naka-pause ang mga tala" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "relais géo indisponibles ; notes en pause" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "ממסרי geo אינם זמינים; הערות הושהו" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जियो रिले अनुपलब्ध; नोट रुके हैं" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "relay geo tidak tersedia; catatan dijeda" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "relay geo non disponibili; note in pausa" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ジオリレーが利用不可: ノート一時停止" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo 릴레이를 사용할 수 없습니다; 노트가 일시 중지됩니다" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "relay geo tidak tersedia; catatan dijeda" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "georelay उपलब्ध छैन; नोट रोकिएको" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo-relays niet beschikbaar; notities gepauzeerd" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo relay niedostępne; notatki wstrzymane" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "relés geográficos indisponíveis; notas em pausa" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "relays geográficos indisponíveis; notas pausadas" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "геореле недоступны; заметки приостановлены" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo-reläer otillgängliga; anteckningar pausade" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo relay கிடைக்கவில்லை; குறிப்புகள் இடைநிறுத்தப்பட்டுள்ளன" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo relay ไม่พร้อมใช้งาน บันทึกถูกหยุดชั่วคราว" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo röleler kullanılamıyor; notlar durduruldu" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "гео-релеї недоступні; замітки призупинено" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "geo relay دستیاب نہیں؛ نوٹس موقوف" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "relay địa lý không sẵn có; ghi chú bị tạm dừng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "地理中继不可用;笔记已暂停" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "地理中繼不可用;筆記已暫停" - } - } - } - }, "location_notes.relays_retry_hint" : { "extractionState" : "manual", "localizations" : { @@ -54886,185 +52354,6 @@ } } }, - "system.common.user" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "مستخدم" - } - }, - "bn" : { - "stringUnit" : { - "state" : "translated", - "value" : "ব্যবহারকারী" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "nutzer" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "user" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "usuario" - } - }, - "fil" : { - "stringUnit" : { - "state" : "translated", - "value" : "user" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "utilisateur" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "משתמש" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "उपयोगकर्ता" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "pengguna" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "utente" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ユーザー" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "사용자" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "pengguna" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "प्रयोगकर्ता" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "gebruiker" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "użytkownik" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "utilizador" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "usuário" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "пользователь" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "användare" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "பயனர்" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ผู้ใช้" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "kullanıcı" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "користувач" - } - }, - "ur" : { - "stringUnit" : { - "state" : "translated", - "value" : "صارف" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "người dùng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "用户" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "使用者" - } - } - } - }, "system.dm.blocked_generic" : { "extractionState" : "manual", "localizations" : { diff --git a/bitchat/Models/BitchatMessage+Media.swift b/bitchat/Models/BitchatMessage+Media.swift index 5706654c..a718e484 100644 --- a/bitchat/Models/BitchatMessage+Media.swift +++ b/bitchat/Models/BitchatMessage+Media.swift @@ -13,13 +13,6 @@ extension BitchatMessage { enum Media { case voice(URL) case image(URL) - - var url: URL { - switch self { - case .voice(let url), .image(let url): - return url - } - } } // Cache the directory lookup to avoid repeated FileManager calls during view rendering diff --git a/bitchat/Models/BitchatPeer.swift b/bitchat/Models/BitchatPeer.swift index 6b69d019..fcccda49 100644 --- a/bitchat/Models/BitchatPeer.swift +++ b/bitchat/Models/BitchatPeer.swift @@ -7,7 +7,6 @@ struct BitchatPeer: Equatable { let peerID: PeerID // Hex-encoded peer ID let noisePublicKey: Data let nickname: String - let lastSeen: Date let isConnected: Bool let isReachable: Bool @@ -77,14 +76,13 @@ struct BitchatPeer: Equatable { peerID: PeerID, noisePublicKey: Data, nickname: String, - lastSeen: Date = Date(), + lastSeen _: Date = Date(), isConnected: Bool = false, isReachable: Bool = false ) { self.peerID = peerID self.noisePublicKey = noisePublicKey self.nickname = nickname - self.lastSeen = lastSeen self.isConnected = isConnected self.isReachable = isReachable diff --git a/bitchat/Noise/NoiseProtocol.swift b/bitchat/Noise/NoiseProtocol.swift index be130f10..a42b86a0 100644 --- a/bitchat/Noise/NoiseProtocol.swift +++ b/bitchat/Noise/NoiseProtocol.swift @@ -723,7 +723,7 @@ final class NoiseHandshakeState { return messageBuffer } - func readMessage(_ message: Data, expectedPayloadLength: Int = 0) throws -> Data { + func readMessage(_ message: Data, expectedPayloadLength _: Int = 0) throws -> Data { guard currentPattern < messagePatterns.count else { throw NoiseError.handshakeComplete diff --git a/bitchat/Noise/NoiseSecurityConstants.swift b/bitchat/Noise/NoiseSecurityConstants.swift index 17781352..67a722b1 100644 --- a/bitchat/Noise/NoiseSecurityConstants.swift +++ b/bitchat/Noise/NoiseSecurityConstants.swift @@ -21,12 +21,6 @@ enum NoiseSecurityConstants { // Maximum number of messages before rekey (2^64 - 1 is the nonce limit) static let maxMessagesPerSession: UInt64 = 1_000_000_000 // 1 billion messages - // Handshake timeout - abandon incomplete handshakes - static let handshakeTimeout: TimeInterval = 60 // 1 minute - - // Maximum concurrent sessions per peer - static let maxSessionsPerPeer = 3 - // Rate limiting static let maxHandshakesPerMinute = 10 static let maxMessagesPerSecond = 100 diff --git a/bitchat/Noise/NoiseSecurityError.swift b/bitchat/Noise/NoiseSecurityError.swift index aff73c8b..8ffba1eb 100644 --- a/bitchat/Noise/NoiseSecurityError.swift +++ b/bitchat/Noise/NoiseSecurityError.swift @@ -14,5 +14,4 @@ enum NoiseSecurityError: Error { case messageTooLarge case invalidPeerID case rateLimitExceeded - case handshakeTimeout } diff --git a/bitchat/Noise/NoiseSessionManager.swift b/bitchat/Noise/NoiseSessionManager.swift index 9029b228..619f84ac 100644 --- a/bitchat/Noise/NoiseSessionManager.swift +++ b/bitchat/Noise/NoiseSessionManager.swift @@ -13,8 +13,6 @@ import BitFoundation final class NoiseSessionManager { private var sessions: [PeerID: NoiseSession] = [:] - private let localStaticKey: Curve25519.KeyAgreement.PrivateKey - private let keychain: KeychainManagerProtocol private let sessionFactory: (PeerID, NoiseRole) -> NoiseSession private let managerQueue = DispatchQueue(label: "chat.bitchat.noise.manager", attributes: .concurrent) @@ -23,8 +21,6 @@ final class NoiseSessionManager { var onSessionFailed: ((PeerID, Error) -> Void)? init(localStaticKey: Curve25519.KeyAgreement.PrivateKey, keychain: KeychainManagerProtocol) { - self.localStaticKey = localStaticKey - self.keychain = keychain self.sessionFactory = { peerID, role in SecureNoiseSession( peerID: peerID, @@ -37,12 +33,10 @@ final class NoiseSessionManager { #if DEBUG init( - localStaticKey: Curve25519.KeyAgreement.PrivateKey, - keychain: KeychainManagerProtocol, + localStaticKey _: Curve25519.KeyAgreement.PrivateKey, + keychain _: KeychainManagerProtocol, sessionFactory: @escaping (PeerID, NoiseRole) -> NoiseSession ) { - self.localStaticKey = localStaticKey - self.keychain = keychain self.sessionFactory = sessionFactory } #endif diff --git a/bitchat/Nostr/NostrIdentity.swift b/bitchat/Nostr/NostrIdentity.swift index 72fb7bbf..7dedfd08 100644 --- a/bitchat/Nostr/NostrIdentity.swift +++ b/bitchat/Nostr/NostrIdentity.swift @@ -6,14 +6,12 @@ struct NostrIdentity: Codable { let privateKey: Data let publicKey: Data let npub: String // Bech32-encoded public key - let createdAt: Date - + /// Memberwise initializer - init(privateKey: Data, publicKey: Data, npub: String, createdAt: Date) { + init(privateKey: Data, publicKey: Data, npub: String, createdAt _: Date) { self.privateKey = privateKey self.publicKey = publicKey self.npub = npub - self.createdAt = createdAt } /// Generate a new Nostr identity @@ -39,12 +37,6 @@ struct NostrIdentity: Codable { self.privateKey = privateKeyData self.publicKey = xOnlyPubkey self.npub = try Bech32.encode(hrp: "npub", data: xOnlyPubkey) - self.createdAt = Date() - } - - /// Get signing key for event signatures - func signingKey() throws -> P256K.Signing.PrivateKey { - try P256K.Signing.PrivateKey(dataRepresentation: privateKey) } /// Get Schnorr signing key for Nostr event signatures diff --git a/bitchat/Nostr/NostrIdentityBridge.swift b/bitchat/Nostr/NostrIdentityBridge.swift index a2e091ba..ab0c89db 100644 --- a/bitchat/Nostr/NostrIdentityBridge.swift +++ b/bitchat/Nostr/NostrIdentityBridge.swift @@ -37,14 +37,6 @@ final class NostrIdentityBridge { return nostrIdentity } - /// Associate a Nostr identity with a Noise public key (for favorites) - func associateNostrIdentity(_ nostrPubkey: String, with noisePublicKey: Data) { - let key = "nostr-noise-\(noisePublicKey.base64EncodedString())" - if let data = nostrPubkey.data(using: .utf8) { - keychain.save(key: key, data: data, service: keychainService, accessible: nil) - } - } - /// Get Nostr public key associated with a Noise public key func getNostrPublicKey(for noisePublicKey: Data) -> String? { let key = "nostr-noise-\(noisePublicKey.base64EncodedString())" diff --git a/bitchat/Nostr/NostrProtocol.swift b/bitchat/Nostr/NostrProtocol.swift index 7e39bdb4..7168b818 100644 --- a/bitchat/Nostr/NostrProtocol.swift +++ b/bitchat/Nostr/NostrProtocol.swift @@ -548,37 +548,6 @@ struct NostrProtocol { return sharedSecretData } - // Direct version that doesn't try to add prefixes - private static func deriveSharedSecretDirect( - privateKey: P256K.Schnorr.PrivateKey, - publicKey: Data - ) throws -> Data { - // Direct shared secret calculation - - // Convert Schnorr private key to KeyAgreement private key - let keyAgreementPrivateKey = try P256K.KeyAgreement.PrivateKey( - dataRepresentation: privateKey.dataRepresentation - ) - - // Use the public key as-is (should already have prefix) - let keyAgreementPublicKey = try P256K.KeyAgreement.PublicKey( - dataRepresentation: publicKey, - format: .compressed - ) - - // Perform ECDH - let sharedSecret = try keyAgreementPrivateKey.sharedSecretFromKeyAgreement( - with: keyAgreementPublicKey, - format: .compressed - ) - - // Convert SharedSecret to Data - let sharedSecretData = sharedSecret.withUnsafeBytes { Data($0) } - - // Return raw ECDH shared secret; HKDF is applied by deriveNIP44V2Key - return sharedSecretData - } - private static func randomizedTimestamp() -> Date { // Add random offset to current time for privacy // This prevents timing correlation attacks while the actual message timestamp @@ -708,11 +677,8 @@ struct NostrEvent: Codable { enum NostrError: Error { case invalidPublicKey - case invalidPrivateKey case invalidEvent case invalidCiphertext - case signingFailed - case encryptionFailed } // MARK: - NIP-44 v2 helpers (XChaCha20-Poly1305) diff --git a/bitchat/Nostr/NostrRelayManager.swift b/bitchat/Nostr/NostrRelayManager.swift index e1de49d6..004ba5f3 100644 --- a/bitchat/Nostr/NostrRelayManager.swift +++ b/bitchat/Nostr/NostrRelayManager.swift @@ -117,7 +117,6 @@ final class NostrRelayManager: ObservableObject { let url: String var isConnected: Bool = false var lastError: Error? - var lastConnectedAt: Date? var messagesSent: Int = 0 var messagesReceived: Int = 0 var reconnectAttempts: Int = 0 @@ -347,7 +346,6 @@ final class NostrRelayManager: ObservableObject { relays[index].nextReconnectTime = nil if resetState { relays[index].lastError = nil - relays[index].lastConnectedAt = nil relays[index].lastDisconnectedAt = nil relays[index].messagesSent = 0 relays[index].messagesReceived = 0 @@ -1075,7 +1073,6 @@ final class NostrRelayManager: ObservableObject { relays[index].isConnected = isConnected relays[index].lastError = error if isConnected { - relays[index].lastConnectedAt = dependencies.now() relays[index].reconnectAttempts = 0 // Reset on successful connection relays[index].nextReconnectTime = nil } else { diff --git a/bitchat/Protocols/Geohash.swift b/bitchat/Protocols/Geohash.swift index 6fbb2b90..d0061c5c 100644 --- a/bitchat/Protocols/Geohash.swift +++ b/bitchat/Protocols/Geohash.swift @@ -10,14 +10,6 @@ enum Geohash { return map }() - /// Validates a geohash string for building-level precision (8 characters). - /// - Parameter geohash: The geohash string to validate - /// - Returns: true if valid 8-character base32 geohash, false otherwise - static func isValidBuildingGeohash(_ geohash: String) -> Bool { - guard geohash.count == 8 else { return false } - return geohash.lowercased().allSatisfy { base32Map[$0] != nil } - } - /// Validates a geohash string at any channel precision (1-12 characters). /// - Parameter geohash: The geohash string to validate /// - Returns: true if a non-empty base32 geohash of at most 12 characters diff --git a/bitchat/Services/AutocompleteService.swift b/bitchat/Services/AutocompleteService.swift index 6b672d53..9ae47df9 100644 --- a/bitchat/Services/AutocompleteService.swift +++ b/bitchat/Services/AutocompleteService.swift @@ -11,14 +11,7 @@ import Foundation /// Manages autocomplete functionality for chat final class AutocompleteService { private let mentionRegex = try? NSRegularExpression(pattern: "@([\\p{L}0-9_]*)$", options: []) - private let commandRegex = try? NSRegularExpression(pattern: "^/([a-z]*)$", options: []) - - private let commands = [ - "/msg", "/who", "/clear", - "/hug", "/slap", "/fav", "/unfav", - "/block", "/unblock" - ] - + /// Get autocomplete suggestions for current text func getSuggestions(for text: String, peers: [String], cursorPosition: Int) -> (suggestions: [String], range: NSRange?) { let textToPosition = String(text.prefix(cursorPosition)) @@ -73,26 +66,6 @@ final class AutocompleteService { return suggestions.isEmpty ? nil : (Array(suggestions), fullRange) } - private func getCommandSuggestions(_ text: String) -> ([String], NSRange)? { - guard let regex = commandRegex else { return nil } - - let nsText = text as NSString - let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsText.length)) - - guard let match = matches.last else { return nil } - - let fullRange = match.range(at: 0) - let captureRange = match.range(at: 1) - let prefix = nsText.substring(with: captureRange).lowercased() - - let suggestions = commands - .filter { $0.hasPrefix("/\(prefix)") } - .sorted() - .prefix(5) - - return suggestions.isEmpty ? nil : (Array(suggestions), fullRange) - } - private func needsArgument(command: String) -> Bool { switch command { case "/who", "/clear": diff --git a/bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift b/bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift index 83311584..9550e196 100644 --- a/bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift +++ b/bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift @@ -61,7 +61,6 @@ struct BLEFragmentAssemblyBuffer { } private struct Metadata { - let type: UInt8 let total: Int let timestamp: Date let isBroadcast: Bool @@ -150,7 +149,6 @@ struct BLEFragmentAssemblyBuffer { fragmentsByKey[header.key] = [:] metadataByKey[header.key] = Metadata( - type: header.originalType, total: header.total, timestamp: now, isBroadcast: header.isBroadcastFragment, diff --git a/bitchat/Services/BLE/BLELogRateLimiter.swift b/bitchat/Services/BLE/BLELogRateLimiter.swift index 65892970..e1e246d8 100644 --- a/bitchat/Services/BLE/BLELogRateLimiter.swift +++ b/bitchat/Services/BLE/BLELogRateLimiter.swift @@ -25,9 +25,4 @@ final class BLELogRateLimiter { } } - func removeAll() { - queue.sync { - lastLogTimeByKey.removeAll() - } - } } diff --git a/bitchat/Services/BLE/BLEOutboundPacketPolicy.swift b/bitchat/Services/BLE/BLEOutboundPacketPolicy.swift index 9449dfa9..ddcc4abc 100644 --- a/bitchat/Services/BLE/BLEOutboundPacketPolicy.swift +++ b/bitchat/Services/BLE/BLEOutboundPacketPolicy.swift @@ -20,7 +20,7 @@ enum BLEOutboundPacketPolicy { } } - static func priority(for packet: BitchatPacket, data: Data) -> BLEOutboundWritePriority { + static func priority(for packet: BitchatPacket, data _: Data) -> BLEOutboundWritePriority { guard let messageType = MessageType(rawValue: packet.type) else { return .low } switch messageType { case .fragment: diff --git a/bitchat/Services/BLE/BLEPeerRegistry.swift b/bitchat/Services/BLE/BLEPeerRegistry.swift index 41ef8098..dc714d13 100644 --- a/bitchat/Services/BLE/BLEPeerRegistry.swift +++ b/bitchat/Services/BLE/BLEPeerRegistry.swift @@ -141,14 +141,6 @@ struct BLEPeerRegistry { } } - func collisionResolvedNickname(for peerID: PeerID, selfNickname: String) -> String? { - guard let info = peers[peerID], info.isVerifiedNickname else { return nil } - let hasCollision = peers.values.contains { - $0.isConnected && $0.nickname == info.nickname && $0.peerID != peerID - } || selfNickname == info.nickname - return hasCollision ? info.nickname + "#" + String(peerID.id.prefix(4)) : info.nickname - } - mutating func markDisconnected(_ peerID: PeerID) { guard var info = peers[peerID] else { return } info.isConnected = false diff --git a/bitchat/Services/BLE/BLEService.swift b/bitchat/Services/BLE/BLEService.swift index 88cb0be2..ba46d7b3 100644 --- a/bitchat/Services/BLE/BLEService.swift +++ b/bitchat/Services/BLE/BLEService.swift @@ -10,7 +10,6 @@ import UIKit /// BLEService — Bluetooth Mesh Transport /// - Emits events exclusively via `BitchatDelegate` for UI. /// - ChatViewModel must consume delegate callbacks (`didReceivePublicMessage`, `didReceiveNoisePayload`). -/// - A lightweight `peerSnapshotPublisher` is provided for non-UI services. final class BLEService: NSObject { // MARK: - Constants @@ -493,13 +492,6 @@ final class BLEService: NSObject { weak var delegate: BitchatDelegate? weak var eventDelegate: TransportEventDelegate? weak var peerEventsDelegate: TransportPeerEventsDelegate? - - // MARK: Peer snapshots publisher (non-UI convenience) - - private let peerSnapshotSubject = PassthroughSubject<[TransportPeerSnapshot], Never>() - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { - peerSnapshotSubject.eraseToAnyPublisher() - } func currentPeerSnapshots() -> [TransportPeerSnapshot] { collectionsQueue.sync { @@ -1323,7 +1315,7 @@ final class BLEService: NSObject { } } - private func handleLeave(_ packet: BitchatPacket, from peerID: PeerID) { + private func handleLeave(_: BitchatPacket, from peerID: PeerID) { _ = collectionsQueue.sync(flags: .barrier) { // Remove the peer when they leave peerRegistry.remove(peerID) @@ -2174,7 +2166,7 @@ extension BLEService: CBPeripheralDelegate { } } - private func processNotificationPacket(_ packet: BitchatPacket, from peripheral: CBPeripheral, peripheralUUID: String, receivedFrom peerID: PeerID) { + private func processNotificationPacket(_ packet: BitchatPacket, from _: CBPeripheral, peripheralUUID: String, receivedFrom peerID: PeerID) { let senderID = PeerID(hexData: packet.senderID) if packet.type != MessageType.announce.rawValue { @@ -3422,7 +3414,7 @@ extension BLEService { /// Transport-level handling for a received nostrCarrier packet; policy /// (verification of the carried event, quotas, loop prevention) lives in /// `GatewayService` behind `onNostrCarrierPacket`. - private func handleNostrCarrier(_ packet: BitchatPacket, from peerID: PeerID) { + private func handleNostrCarrier(_ packet: BitchatPacket, from _: PeerID) { let senderID = PeerID(hexData: packet.senderID) let directedToUs: Bool if let recipientID = packet.recipientID { @@ -4476,7 +4468,7 @@ extension BLEService { /// gossip backfill and hand the payload to the UI layer, where the group /// coordinator decrypts and authenticates against the roster. Non-members /// still relay (generic broadcast relay path) but never decode. - private func handleGroupMessage(_ packet: BitchatPacket, from peerID: PeerID) { + private func handleGroupMessage(_ packet: BitchatPacket, from _: PeerID) { let isBroadcastRecipient: Bool = { guard let recipient = packet.recipientID else { return true } return recipient.count == 8 && recipient.allSatisfy { $0 == 0xFF } @@ -4636,8 +4628,6 @@ extension BLEService { let transportPeers: [TransportPeerSnapshot] = collectionsQueue.sync { peerRegistry.transportSnapshots(selfNickname: myNickname) } - // Notify non-UI listeners - peerSnapshotSubject.send(transportPeers) // Notify UI on MainActor via delegate Task { @MainActor [weak self] in self?.peerEventsDelegate?.didUpdatePeerSnapshots(transportPeers) diff --git a/bitchat/Services/Board/BoardManager.swift b/bitchat/Services/Board/BoardManager.swift index 1f307bd4..8c758fa8 100644 --- a/bitchat/Services/Board/BoardManager.swift +++ b/bitchat/Services/Board/BoardManager.swift @@ -19,7 +19,6 @@ final class BoardManager: ObservableObject { @Published private(set) var posts: [BoardPostPacket] = [] private let transport: Transport - private let store: BoardStore /// Publishes a bridged kind-1 note (expiring with the board post via /// NIP-40) and returns its Nostr event id, or nil when bridging failed or /// was skipped. @@ -39,7 +38,6 @@ final class BoardManager: ObservableObject { deleteFromNostr: ((String, String) -> Void)? = nil ) { self.transport = transport - self.store = store self.publishToNostr = publishToNostr ?? Self.livePublishToNostr self.deleteFromNostr = deleteFromNostr ?? Self.liveDeleteFromNostr cancellable = store.$postsSnapshot diff --git a/bitchat/Services/Board/BoardStore.swift b/bitchat/Services/Board/BoardStore.swift index c96670d0..db6a9605 100644 --- a/bitchat/Services/Board/BoardStore.swift +++ b/bitchat/Services/Board/BoardStore.swift @@ -146,14 +146,6 @@ final class BoardStore { // MARK: - Maintenance - func pruneExpired() { - let nowMs = currentMs() - queue.sync { - pruneExpiredLocked(nowMs: nowMs) - persistLocked() - } - } - /// Panic wipe: drop all board data from memory and disk. func wipe() { queue.sync { diff --git a/bitchat/Services/Courier/CourierStore.swift b/bitchat/Services/Courier/CourierStore.swift index a24eb371..06bfc7c1 100644 --- a/bitchat/Services/Courier/CourierStore.swift +++ b/bitchat/Services/Courier/CourierStore.swift @@ -272,14 +272,6 @@ final class CourierStore { // MARK: - Maintenance - func pruneExpired() { - let date = now() - queue.sync { - pruneExpiredLocked(at: date) - persistLocked() - } - } - /// Panic wipe: drop all carried mail from memory and disk. func wipe() { queue.sync { diff --git a/bitchat/Services/Courier/StoreAndForwardMetrics.swift b/bitchat/Services/Courier/StoreAndForwardMetrics.swift index 5452f357..4fddf251 100644 --- a/bitchat/Services/Courier/StoreAndForwardMetrics.swift +++ b/bitchat/Services/Courier/StoreAndForwardMetrics.swift @@ -58,12 +58,6 @@ final class StoreAndForwardMetrics { SecureLogger.debug("📊 S&F \(event.rawValue) → \(total)", category: .session) } - func snapshot() -> [String: Int] { - lock.lock() - defer { lock.unlock() } - return counts - } - /// Included in the panic wipe alongside the stores it describes. func reset() { lock.lock() diff --git a/bitchat/Services/Gateway/GatewayService.swift b/bitchat/Services/Gateway/GatewayService.swift index c8d13756..3b5ec30a 100644 --- a/bitchat/Services/Gateway/GatewayService.swift +++ b/bitchat/Services/Gateway/GatewayService.swift @@ -82,7 +82,6 @@ final class GatewayService: ObservableObject { let depositor: PeerID let geohash: String let event: NostrEvent - let queuedAt: Date } static let shared = GatewayService() @@ -221,7 +220,7 @@ final class GatewayService: ObservableObject { publish(event, geohash: carrier.geohash) accepted = true } else { - accepted = enqueueUplink(QueuedUplink(depositor: depositor, geohash: carrier.geohash, event: event, queuedAt: now())) + accepted = enqueueUplink(QueuedUplink(depositor: depositor, geohash: carrier.geohash, event: event)) } // Only render on our own timeline what we actually accepted for diff --git a/bitchat/Services/GeohashParticipantTracker.swift b/bitchat/Services/GeohashParticipantTracker.swift index 0e46ad34..be1ea35e 100644 --- a/bitchat/Services/GeohashParticipantTracker.swift +++ b/bitchat/Services/GeohashParticipantTracker.swift @@ -9,12 +9,12 @@ import Foundation /// Represents a participant in a geohash channel -public struct GeoPerson: Identifiable, Equatable, Sendable { - public let id: String // pubkey hex (lowercased) - public let displayName: String - public let lastSeen: Date +struct GeoPerson: Identifiable, Equatable, Sendable { + let id: String // pubkey hex (lowercased) + let displayName: String + let lastSeen: Date - public init(id: String, displayName: String, lastSeen: Date) { + init(id: String, displayName: String, lastSeen: Date) { self.id = id self.displayName = displayName self.lastSeen = lastSeen @@ -23,7 +23,7 @@ public struct GeoPerson: Identifiable, Equatable, Sendable { /// Protocol for resolving display names and checking block status @MainActor -public protocol GeohashParticipantContext: AnyObject { +protocol GeohashParticipantContext: AnyObject { /// Returns display name for a Nostr pubkey (e.g., "alice#a1b2" or "anon#c3d4") func displayNameForPubkey(_ pubkeyHex: String) -> String /// Returns true if the pubkey is blocked @@ -32,16 +32,16 @@ public protocol GeohashParticipantContext: AnyObject { /// Tracks participants across multiple geohash channels @MainActor -public final class GeohashParticipantTracker: ObservableObject { +final class GeohashParticipantTracker: ObservableObject { /// Activity cutoff duration (defaults to 5 minutes) - public let activityCutoff: TimeInterval + let activityCutoff: TimeInterval /// Per-geohash participant map: [geohash: [pubkeyHex: lastSeen]] private var participants: [String: [String: Date]] = [:] /// Currently visible people for the active geohash - @Published public private(set) var visiblePeople: [GeoPerson] = [] + @Published private(set) var visiblePeople: [GeoPerson] = [] /// The currently active geohash (if any) private var activeGeohash: String? @@ -52,17 +52,17 @@ public final class GeohashParticipantTracker: ObservableObject { /// Timer for periodic refresh private var refreshTimer: Timer? - public init(activityCutoff: TimeInterval = -300) { // default 5 minutes + init(activityCutoff: TimeInterval = -300) { // default 5 minutes self.activityCutoff = activityCutoff } /// Configure with a context provider - public func configure(context: GeohashParticipantContext) { + func configure(context: GeohashParticipantContext) { self.context = context } /// Set the currently active geohash - public func setActiveGeohash(_ geohash: String?) { + func setActiveGeohash(_ geohash: String?) { activeGeohash = geohash if geohash == nil { visiblePeople = [] @@ -72,13 +72,13 @@ public final class GeohashParticipantTracker: ObservableObject { } /// Record activity from a participant in the current active geohash - public func recordParticipant(pubkeyHex: String) { + func recordParticipant(pubkeyHex: String) { guard let gh = activeGeohash else { return } recordParticipant(pubkeyHex: pubkeyHex, geohash: gh) } /// Record activity from a participant in a specific geohash - public func recordParticipant(pubkeyHex: String, geohash: String) { + func recordParticipant(pubkeyHex: String, geohash: String) { let key = pubkeyHex.lowercased() var map = participants[geohash] ?? [:] map[key] = Date() @@ -94,7 +94,7 @@ public final class GeohashParticipantTracker: ObservableObject { } /// Remove a participant from all geohashes (used when blocking) - public func removeParticipant(pubkeyHex: String) { + func removeParticipant(pubkeyHex: String) { let key = pubkeyHex.lowercased() for (gh, var map) in participants { map.removeValue(forKey: key) @@ -104,14 +104,14 @@ public final class GeohashParticipantTracker: ObservableObject { } /// Get participant count for a specific geohash - public func participantCount(for geohash: String) -> Int { + func participantCount(for geohash: String) -> Int { let cutoff = Date().addingTimeInterval(activityCutoff) let map = participants[geohash] ?? [:] return map.values.filter { $0 >= cutoff }.count } /// Get the visible people list for the active geohash (read-only query) - public func getVisiblePeople() -> [GeoPerson] { + func getVisiblePeople() -> [GeoPerson] { guard let gh = activeGeohash, let context = context else { return [] } let cutoff = Date().addingTimeInterval(activityCutoff) let map = (participants[gh] ?? [:]) @@ -126,12 +126,12 @@ public final class GeohashParticipantTracker: ObservableObject { } /// Refresh the visible people list - public func refresh() { + func refresh() { visiblePeople = getVisiblePeople() } /// Start the periodic refresh timer - public func startRefreshTimer(interval: TimeInterval = 30.0) { + func startRefreshTimer(interval: TimeInterval = 30.0) { stopRefreshTimer() refreshTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in Task { @MainActor in @@ -141,19 +141,19 @@ public final class GeohashParticipantTracker: ObservableObject { } /// Stop the periodic refresh timer - public func stopRefreshTimer() { + func stopRefreshTimer() { refreshTimer?.invalidate() refreshTimer = nil } /// Clear all participant data - public func clear() { + func clear() { participants.removeAll() visiblePeople = [] } /// Clear participant data for a specific geohash - public func clear(geohash: String) { + func clear(geohash: String) { participants.removeValue(forKey: geohash) if activeGeohash == geohash { visiblePeople = [] diff --git a/bitchat/Services/Groups/GroupProtocol.swift b/bitchat/Services/Groups/GroupProtocol.swift index dc4a0abf..8f4a09d1 100644 --- a/bitchat/Services/Groups/GroupProtocol.swift +++ b/bitchat/Services/Groups/GroupProtocol.swift @@ -406,7 +406,6 @@ enum GroupCryptoError: Error, Equatable { case malformedPayload case signingFailed case sealFailed - case wrongEpoch case decryptionFailed case badSenderSignature } diff --git a/bitchat/Services/KeychainManager.swift b/bitchat/Services/KeychainManager.swift index 54d2ff93..39035547 100644 --- a/bitchat/Services/KeychainManager.swift +++ b/bitchat/Services/KeychainManager.swift @@ -255,11 +255,6 @@ final class KeychainManager: KeychainManagerProtocol { // MARK: - Generic Operations - private func save(_ value: String, forKey key: String) -> Bool { - guard let data = value.data(using: .utf8) else { return false } - return saveData(data, forKey: key) - } - private func saveData(_ data: Data, forKey key: String) -> Bool { // Delete any existing item first to ensure clean state _ = delete(forKey: key) @@ -305,11 +300,6 @@ final class KeychainManager: KeychainManagerProtocol { return false } - private func retrieve(forKey key: String) -> String? { - guard let data = retrieveData(forKey: key) else { return nil } - return String(data: data, encoding: .utf8) - } - private func retrieveData(forKey key: String) -> Data? { // Base query let base: [String: Any] = [ @@ -365,22 +355,7 @@ final class KeychainManager: KeychainManagerProtocol { } // MARK: - Cleanup - - func deleteAllPasswords() -> Bool { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword - ] - - // Add service if not empty - if !service.isEmpty { - query[kSecAttrService as String] = service - } - - let status = SecItemDelete(query as CFDictionary) - return status == errSecSuccess || status == errSecItemNotFound - } - - + // Delete ALL keychain data for panic mode func deleteAllKeychainData() -> Bool { SecureLogger.warning("Panic mode - deleting all keychain data", category: .security) diff --git a/bitchat/Services/LocationStateManager.swift b/bitchat/Services/LocationStateManager.swift index aca9fb87..36fb4bc4 100644 --- a/bitchat/Services/LocationStateManager.swift +++ b/bitchat/Services/LocationStateManager.swift @@ -101,9 +101,7 @@ final class LocationStateManager: NSObject, CLLocationManagerDelegate, Observabl private let cl: LocationStateManaging private let geocoder: LocationStateGeocoding - private var lastLocation: CLLocation? private var refreshTimer: Timer? - private var isGeocoding: Bool = false // MARK: - Persistence Keys @@ -268,7 +266,7 @@ final class LocationStateManager: NSObject, CLLocationManagerDelegate, Observabl } } - func beginLiveRefresh(interval: TimeInterval = TransportConfig.locationLiveRefreshInterval) { + func beginLiveRefresh(interval _: TimeInterval = TransportConfig.locationLiveRefreshInterval) { guard permissionState == .authorized else { return } refreshTimer?.invalidate() refreshTimer = nil @@ -385,7 +383,6 @@ final class LocationStateManager: NSObject, CLLocationManagerDelegate, Observabl func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let loc = locations.last else { return } - lastLocation = loc computeChannels(from: loc.coordinate) reverseGeocodeLocation(loc) } @@ -441,10 +438,8 @@ final class LocationStateManager: NSObject, CLLocationManagerDelegate, Observabl private func reverseGeocodeLocation(_ location: CLLocation) { geocoder.cancelGeocode() - isGeocoding = true geocoder.reverseGeocodeLocation(location) { [weak self] placemarks, _ in guard let self = self else { return } - self.isGeocoding = false if let pm = placemarks?.first { let names = self.locationNamesByLevel(from: pm) Task { @MainActor in self.locationNames = names } @@ -632,15 +627,5 @@ extension LocationStateManager { func toggle(_ geohash: String) { toggleBookmark(geohash) } - - /// Backward compatibility: add bookmark (was GeohashBookmarksStore.add) - func add(_ geohash: String) { - addBookmark(geohash) - } - - /// Backward compatibility: remove bookmark (was GeohashBookmarksStore.remove) - func remove(_ geohash: String) { - removeBookmark(geohash) - } } #endif diff --git a/bitchat/Services/MessageFormattingEngine.swift b/bitchat/Services/MessageFormattingEngine.swift index 71abcbe4..4bd4b073 100644 --- a/bitchat/Services/MessageFormattingEngine.swift +++ b/bitchat/Services/MessageFormattingEngine.swift @@ -70,10 +70,6 @@ final class MessageFormattingEngine { static let quickCashuPresence: NSRegularExpression = { try! NSRegularExpression(pattern: "\\bcashu[AB][A-Za-z0-9._-]{40,}\\b", options: []) }() - - static let simplifyHTTPURL: NSRegularExpression = { - try! NSRegularExpression(pattern: "https?://[^\\s?#]+(?:[?#][^\\s]*)?", options: [.caseInsensitive]) - }() } // MARK: - Match Types @@ -195,7 +191,7 @@ final class MessageFormattingEngine { // MARK: - Private Helpers - private static func formatSystemMessage(_ message: BitchatMessage, isDark: Bool) -> AttributedString { + private static func formatSystemMessage(_ message: BitchatMessage, isDark _: Bool) -> AttributedString { var result = AttributedString() let content = AttributedString("* \(message.content) *") @@ -414,7 +410,7 @@ final class MessageFormattingEngine { return AttributedString(text).mergingAttributes(style) } - private static func formatMatch(_ text: String, type: MatchType, baseColor: Color, isSelf: Bool) -> AttributedString { + private static func formatMatch(_ text: String, type: MatchType, baseColor _: Color, isSelf _: Bool) -> AttributedString { var style = AttributeContainer() switch type { diff --git a/bitchat/Services/MessageRouter.swift b/bitchat/Services/MessageRouter.swift index 976b8eb8..54b53ee8 100644 --- a/bitchat/Services/MessageRouter.swift +++ b/bitchat/Services/MessageRouter.swift @@ -311,13 +311,6 @@ final class MessageRouter { } } - func sendDeliveryAck(_ messageID: String, to peerID: PeerID) { - if let transport = reachableTransport(for: peerID) { - SecureLogger.debug("Routing DELIVERED ack via \(type(of: transport)) to \(peerID.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session) - transport.sendDeliveryAck(for: messageID, to: peerID) - } - } - func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) { if let transport = connectedTransport(for: peerID) { transport.sendFavoriteNotification(to: peerID, isFavorite: isFavorite) diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index 9e7e6eae..3e0aae6b 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -153,11 +153,11 @@ enum EncryptionStatus: Equatable { final class NoiseEncryptionService { // Static identity key (persistent across sessions) private let staticIdentityKey: Curve25519.KeyAgreement.PrivateKey - public let staticIdentityPublicKey: Curve25519.KeyAgreement.PublicKey + let staticIdentityPublicKey: Curve25519.KeyAgreement.PublicKey // Ed25519 signing key (persistent across sessions) private let signingKey: Curve25519.Signing.PrivateKey - public let signingPublicKey: Curve25519.Signing.PublicKey + let signingPublicKey: Curve25519.Signing.PublicKey // Session manager private let sessionManager: NoiseSessionManager diff --git a/bitchat/Services/NostrTransport.swift b/bitchat/Services/NostrTransport.swift index 9dd6ad68..dc8418a8 100644 --- a/bitchat/Services/NostrTransport.swift +++ b/bitchat/Services/NostrTransport.swift @@ -13,7 +13,6 @@ final class NostrTransport: Transport, @unchecked Sendable { let currentIdentity: @MainActor () throws -> NostrIdentity? let registerPendingGiftWrap: @MainActor (String) -> Void let sendEvent: @MainActor (NostrEvent) -> Void - let scheduleAfter: @Sendable (TimeInterval, @escaping @Sendable () -> Void) -> Void /// Emits whether a relay that carries private messages is up /// (fail-closed behind Tor). A connected geohash/custom relay alone /// doesn't count: DM sends target the default relay set and would @@ -42,7 +41,6 @@ final class NostrTransport: Transport, @unchecked Sendable { self.currentIdentity = currentIdentity self.registerPendingGiftWrap = registerPendingGiftWrap self.sendEvent = sendEvent - self.scheduleAfter = scheduleAfter self.relayConnectivity = relayConnectivity // Default pacer drives its throttle through the same injected // scheduler, so tests that step scheduleAfter manually keep @@ -130,8 +128,6 @@ final class NostrTransport: Transport, @unchecked Sendable { } } static let sharedAckPacer = AckPacer() - private let keychain: KeychainManagerProtocol - private let idBridge: NostrIdentityBridge private let dependencies: Dependencies private var favoriteStatusObserver: NSObjectProtocol? @@ -145,12 +141,10 @@ final class NostrTransport: Transport, @unchecked Sendable { @MainActor init( - keychain: KeychainManagerProtocol, + keychain _: KeychainManagerProtocol, idBridge: NostrIdentityBridge, dependencies: Dependencies? = nil ) { - self.keychain = keychain - self.idBridge = idBridge self.dependencies = dependencies ?? .live(idBridge: idBridge) setupObservers() @@ -207,9 +201,6 @@ final class NostrTransport: Transport, @unchecked Sendable { weak var eventDelegate: TransportEventDelegate? weak var peerEventsDelegate: TransportPeerEventsDelegate? - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { - Just([]).eraseToAnyPublisher() - } func currentPeerSnapshots() -> [TransportPeerSnapshot] { [] } var myPeerID: PeerID { senderPeerID } diff --git a/bitchat/Services/PrivateChatManager.swift b/bitchat/Services/PrivateChatManager.swift index c979b073..513acebb 100644 --- a/bitchat/Services/PrivateChatManager.swift +++ b/bitchat/Services/PrivateChatManager.swift @@ -28,7 +28,6 @@ final class PrivateChatManager: ObservableObject { @Published private(set) var selectedPeer: PeerID? = nil private var selectedPeerMirrorCancellable: AnyCancellable? = nil - private var selectedPeerFingerprint: String? = nil var sentReadReceipts: Set = [] // Made accessible for ChatViewModel weak var meshService: Transport? @@ -219,18 +218,13 @@ final class PrivateChatManager: ObservableObject { /// Start a private chat with a peer. Selection is mutated through the /// store's intent (the store owns it); the manager keeps its side - /// effects (fingerprint tracking, read receipts, unread clearing). + /// effects (read receipts, unread clearing). @MainActor func startChat(with peerID: PeerID) { // Also creates the conversation if needed and updates the derived // `selectedConversationID`; `selectedPeer` mirrors the change. conversationStore?.setSelectedPrivatePeer(peerID) - // Store fingerprint for persistence across reconnections - if let fingerprint = meshService?.getFingerprint(for: peerID) { - selectedPeerFingerprint = fingerprint - } - // Mark messages as read markAsRead(from: peerID) } @@ -239,15 +233,8 @@ final class PrivateChatManager: ObservableObject { /// channel's conversation). func endChat() { conversationStore?.setSelectedPrivatePeer(nil) - selectedPeerFingerprint = nil } - /// No-op since the `ConversationStore` cutover: the store maintains - /// chronological order and dedups by message ID on every insert, so the - /// per-append re-sort/dedup sweep this performed is no longer needed. - /// Kept only for API compatibility until step 5 removes the callers. - func sanitizeChat(for peerID: PeerID) {} - /// Mark messages from a peer as read @MainActor func markAsRead(from peerID: PeerID) { diff --git a/bitchat/Services/RelayController.swift b/bitchat/Services/RelayController.swift index dc87fabf..7dbcf4ab 100644 --- a/bitchat/Services/RelayController.swift +++ b/bitchat/Services/RelayController.swift @@ -12,7 +12,7 @@ struct RelayController { static func decide(ttl: UInt8, senderIsSelf: Bool, recipientIsSelf: Bool = false, - isEncrypted: Bool, + isEncrypted _: Bool, isDirectedEncrypted: Bool, isFragment: Bool, isDirectedFragment: Bool, diff --git a/bitchat/Services/TransferProgressManager.swift b/bitchat/Services/TransferProgressManager.swift index 3ab721ba..4c6a34d8 100644 --- a/bitchat/Services/TransferProgressManager.swift +++ b/bitchat/Services/TransferProgressManager.swift @@ -49,12 +49,6 @@ final class TransferProgressManager { } } - func reset(id: String) { - queue.async(flags: .barrier) { [weak self] in - self?.states.removeValue(forKey: id) - } - } - func snapshot(id: String) -> (sent: Int, total: Int)? { var result: (sent: Int, total: Int)? queue.sync { diff --git a/bitchat/Services/Transport.swift b/bitchat/Services/Transport.swift index 82bcc166..62712db7 100644 --- a/bitchat/Services/Transport.swift +++ b/bitchat/Services/Transport.swift @@ -96,7 +96,6 @@ protocol Transport: AnyObject { var peerEventsDelegate: TransportPeerEventsDelegate? { get set } // Peer snapshots (for non-UI services) - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { get } func currentPeerSnapshots() -> [TransportPeerSnapshot] // Identity @@ -265,7 +264,7 @@ extension Transport { } protocol TransportPeerEventsDelegate: AnyObject { - @MainActor func didUpdatePeerSnapshots(_ peers: [TransportPeerSnapshot]) + @MainActor func didUpdatePeerSnapshots(_: [TransportPeerSnapshot]) } extension BitchatDelegate { diff --git a/bitchat/Services/TransportConfig.swift b/bitchat/Services/TransportConfig.swift index b9603564..22069296 100644 --- a/bitchat/Services/TransportConfig.swift +++ b/bitchat/Services/TransportConfig.swift @@ -27,7 +27,6 @@ enum TransportConfig { static let pttJitterBufferSeconds: TimeInterval = 0.35 // buffered audio before live playback starts static let pttJitterDeadlineSeconds: TimeInterval = 0.5 // start anyway after this wall-clock wait static let pttBurstEndTimeoutSeconds: TimeInterval = 3.0 // no frames -> burst considered ended - static let pttAssemblyStaleSeconds: TimeInterval = 30.0 // drop half-open assemblies after this static let pttMaxConcurrentAssemblies: Int = 8 // concurrent inbound bursts cap static let pttMaxBurstBytes: Int = 384 * 1024 // 120s at ~2KB/s + generous slack static let pttFinishedBurstRegistrySeconds: TimeInterval = 600 // window to absorb the finalized note @@ -93,8 +92,7 @@ enum TransportConfig { // UI thresholds static let uiProcessedNostrEventsCap: Int = 2000 - static let uiChannelInactivityThresholdSeconds: TimeInterval = 9 * 60 - + // UI rate limiters (token buckets) static let uiSenderRateBucketCapacity: Double = 5 static let uiSenderRateBucketRefillPerSec: Double = 1.0 @@ -103,17 +101,13 @@ enum TransportConfig { // UI sleeps/delays static let uiStartupInitialDelaySeconds: TimeInterval = 1.0 - static let uiStartupShortSleepNs: UInt64 = 200_000_000 static let uiStartupPhaseDurationSeconds: TimeInterval = 2.0 static let uiAsyncShortSleepNs: UInt64 = 100_000_000 - static let uiAsyncMediumSleepNs: UInt64 = 500_000_000 static let uiReadReceiptRetryShortSeconds: TimeInterval = 0.1 static let uiReadReceiptRetryLongSeconds: TimeInterval = 0.5 static let uiBatchDispatchStaggerSeconds: TimeInterval = 0.15 static let uiScrollThrottleSeconds: TimeInterval = 0.5 - static let uiAnimationShortSeconds: TimeInterval = 0.15 static let uiAnimationMediumSeconds: TimeInterval = 0.2 - static let uiAnimationSidebarSeconds: TimeInterval = 0.25 static let uiRecentCutoffFiveMinutesSeconds: TimeInterval = 5 * 60 static let uiMeshEmptyConfirmationSeconds: TimeInterval = 30.0 @@ -179,10 +173,6 @@ enum TransportConfig { static let nostrGeohashSampleLimit: Int = 100 static let nostrDMSubscribeLookbackSeconds: TimeInterval = 86400 - // Nostr helpers - static let nostrShortKeyDisplayLength: Int = 8 - static let nostrConvKeyPrefixLength: Int = 16 - // Message deduplication static let messageDedupMaxAgeSeconds: TimeInterval = 300 static let messageDedupMaxCount: Int = 1000 @@ -294,12 +284,7 @@ enum TransportConfig { static let uiVeryLongTokenThreshold: Int = 512 static let uiLongMessageLineLimit: Int = 30 static let uiFingerprintSampleCount: Int = 3 - - // UI swipe/gesture thresholds - static let uiBackSwipeTranslationLarge: CGFloat = 50 - static let uiBackSwipeTranslationSmall: CGFloat = 30 - static let uiBackSwipeVelocityThreshold: CGFloat = 300 - + // UI color tuning static let uiColorHueAvoidanceDelta: Double = 0.05 static let uiColorHueOffset: Double = 0.12 diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index a5d64621..84a4843f 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -70,7 +70,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } // TransportPeerEventsDelegate - func didUpdatePeerSnapshots(_ peers: [TransportPeerSnapshot]) { + func didUpdatePeerSnapshots(_: [TransportPeerSnapshot]) { updatePeers() } @@ -367,12 +367,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } // MARK: - Compatibility Methods (for easy migration) - - var allPeers: [BitchatPeer] { peers } - var connectedPeers: Set { connectedPeerIDs } - var favoritePeers: Set { - Set(favorites.compactMap { getFingerprint(for: $0.peerID) }) - } + var blockedUsers: Set { Set(peers.compactMap { peer in isBlocked(peer.peerID) ? getFingerprint(for: peer.peerID) : nil diff --git a/bitchat/Utils/MessageDeduplicator.swift b/bitchat/Utils/MessageDeduplicator.swift index 00ea0eb5..215384fa 100644 --- a/bitchat/Utils/MessageDeduplicator.swift +++ b/bitchat/Utils/MessageDeduplicator.swift @@ -52,18 +52,6 @@ final class MessageDeduplicator { return false } - /// Record an ID with a specific timestamp (for content key tracking) - func record(_ id: String, timestamp: Date) { - lock.lock() - defer { lock.unlock() } - - if lookup[id] == nil { - entries.append(Entry(id: id, timestamp: timestamp)) - } - lookup[id] = timestamp - trimIfNeeded() - } - /// Add an ID without checking (for announce-back tracking) func markProcessed(_ id: String) { lock.lock() @@ -83,13 +71,6 @@ final class MessageDeduplicator { return lookup[id] != nil } - /// Get timestamp for an ID (for content deduplication time-window checks) - func timestampFor(_ id: String) -> Date? { - lock.lock() - defer { lock.unlock() } - return lookup[id] - } - private func trimIfNeeded() { let activeCount = entries.count - head guard activeCount > maxCount else { return } diff --git a/bitchat/Utils/Theme.swift b/bitchat/Utils/Theme.swift index 40fe035f..ec0283e0 100644 --- a/bitchat/Utils/Theme.swift +++ b/bitchat/Utils/Theme.swift @@ -96,7 +96,7 @@ struct ThemePalette { ) } - static func liquidGlass(_ colorScheme: ColorScheme) -> ThemePalette { + static func liquidGlass(_: ColorScheme) -> ThemePalette { ThemePalette( background: systemBackground, primary: .primary, diff --git a/bitchat/ViewModels/ChatGroupCoordinator.swift b/bitchat/ViewModels/ChatGroupCoordinator.swift index a83cb024..7489b3bc 100644 --- a/bitchat/ViewModels/ChatGroupCoordinator.swift +++ b/bitchat/ViewModels/ChatGroupCoordinator.swift @@ -387,7 +387,7 @@ final class ChatGroupCoordinator { /// Decrypt-verify path for an incoming 0x25 broadcast. Drops silently for /// unknown groups (non-members relay but never read), wrong epochs, bad /// sender signatures, and senders missing from the pinned roster. - func handleGroupMessagePayload(_ payload: Data, timestamp: Date) { + func handleGroupMessagePayload(_ payload: Data, timestamp _: Date) { guard let envelope = GroupMessageEnvelope.decode(payload) else { return } guard let group = context.groupStore.group(withID: envelope.groupID) else { return } guard envelope.epoch == group.epoch else { diff --git a/bitchat/ViewModels/ChatPeerIdentityCoordinator.swift b/bitchat/ViewModels/ChatPeerIdentityCoordinator.swift index 30d566b8..a1e340fe 100644 --- a/bitchat/ViewModels/ChatPeerIdentityCoordinator.swift +++ b/bitchat/ViewModels/ChatPeerIdentityCoordinator.swift @@ -29,7 +29,6 @@ protocol ChatPeerIdentityContext: AnyObject { func migratePrivateChat(from oldPeerID: PeerID, to newPeerID: PeerID) var selectedPrivateChatPeer: PeerID? { get set } var selectedPrivateChatFingerprint: String? { get set } - var nickname: String { get } var myPeerID: PeerID { get } var activeChannel: ChannelID { get } /// Signals that message state changed so observers refresh (e.g. `objectWillChange.send()`). @@ -104,7 +103,7 @@ protocol ChatPeerIdentityContext: AnyObject { extension ChatViewModel: ChatPeerIdentityContext { // `privateChats`, `unreadPrivateMessages`, `selectedPrivateChatPeer`, - // `selectedPrivateChatFingerprint`, `nickname`, `myPeerID`, + // `selectedPrivateChatFingerprint`, `myPeerID`, // `activeChannel`, `connectedPeers`, `geoNicknames`, `notifyUIChanged()`, // `addSystemMessage(_:)`, `peerNickname(for:)`, `meshPeerNicknames()`, // `ephemeralPeerID(forNoiseKey:)`, `unifiedPeer(for:)`, diff --git a/bitchat/ViewModels/ChatPrivateConversationCoordinator.swift b/bitchat/ViewModels/ChatPrivateConversationCoordinator.swift index 73b784aa..6765746a 100644 --- a/bitchat/ViewModels/ChatPrivateConversationCoordinator.swift +++ b/bitchat/ViewModels/ChatPrivateConversationCoordinator.swift @@ -19,7 +19,6 @@ protocol ChatPrivateConversationContext: AnyObject { /// lookup on `ChatViewModel` (no `privateChats` dictionary build). func privateMessages(for peerID: PeerID) -> [BitchatMessage] var sentReadReceipts: Set { get } - var unreadPrivateMessages: Set { get } var selectedPrivateChatPeer: PeerID? { get } var nickname: String { get } var activeChannel: ChannelID { get } @@ -40,8 +39,6 @@ protocol ChatPrivateConversationContext: AnyObject { func setPrivateDeliveryStatus(_ status: DeliveryStatus, forMessageID messageID: String, peerID: PeerID) -> Bool func markPrivateChatUnread(_ peerID: PeerID) func markPrivateChatRead(_ peerID: PeerID) - /// Removes the peer's chat entirely, including unread state. - func removePrivateChat(_ peerID: PeerID) /// Moves all messages from `oldPeerID`'s chat into `newPeerID`'s chat /// (dedup by ID, order preserved, unread carried, old chat removed). func migratePrivateChat(from oldPeerID: PeerID, to newPeerID: PeerID) @@ -87,7 +84,6 @@ protocol ChatPrivateConversationContext: AnyObject { // MARK: Routing & acknowledgements func routePrivateMessage(_ content: String, to peerID: PeerID, recipientNickname: String, messageID: String) func routeReadReceipt(_ receipt: ReadReceipt, to peerID: PeerID) - func routeFavoriteNotification(to peerID: PeerID, isFavorite: Bool) func sendMeshReadReceipt(_ receipt: ReadReceipt, to peerID: PeerID) func sendGeohashPrivateMessage(_ content: String, toRecipientHex recipientHex: String, from identity: NostrIdentity, messageID: String) func sendGeohashDeliveryAck(for messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) @@ -606,7 +602,7 @@ final class ChatPrivateConversationCoordinator { /// O(1)-per-conversation dedup via the store's message-ID indexes /// (replaces the full scan over every private chat). - func isDuplicateMessage(_ messageId: String, targetPeerID: PeerID) -> Bool { + func isDuplicateMessage(_ messageId: String, targetPeerID _: PeerID) -> Bool { context.privateChatsContainMessage(withID: messageId) } @@ -825,25 +821,6 @@ final class ChatPrivateConversationCoordinator { } } - func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) { - var noiseKey: Data? - - if let hexKey = Data(hexString: peerID.id) { - noiseKey = hexKey - } else if let peerNoiseKey = context.noisePublicKey(for: peerID) { - noiseKey = peerNoiseKey - } - - if context.isPeerConnected(peerID) { - context.routeFavoriteNotification(to: peerID, isFavorite: isFavorite) - SecureLogger.debug("📤 Sent favorite notification via BLE to \(peerID)", category: .session) - } else if let key = noiseKey { - context.routeFavoriteNotification(to: PeerID(hexData: key), isFavorite: isFavorite) - } else { - SecureLogger.warning("⚠️ Cannot send favorite notification - peer not connected and no Nostr pubkey", category: .session) - } - } - func isMessageBlocked(_ message: BitchatMessage) -> Bool { if let peerID = message.senderPeerID ?? context.getPeerIDForNickname(message.sender) { if context.isPeerBlocked(peerID) { return true } diff --git a/bitchat/ViewModels/ChatPublicConversationCoordinator.swift b/bitchat/ViewModels/ChatPublicConversationCoordinator.swift index c5f19c44..f5f57cf1 100644 --- a/bitchat/ViewModels/ChatPublicConversationCoordinator.swift +++ b/bitchat/ViewModels/ChatPublicConversationCoordinator.swift @@ -36,9 +36,6 @@ protocol ChatPublicConversationContext: AnyObject { /// message with the same ID is already in that conversation. @discardableResult func appendPublicMessage(_ message: BitchatMessage, to conversationID: ConversationID) -> Bool - /// Appends a geohash message if absent. Returns `true` when stored. - @discardableResult - func appendGeohashMessageIfAbsent(_ message: BitchatMessage, toGeohash geohash: String) -> Bool func publicConversationContainsMessage(withID messageID: String, in conversationID: ConversationID) -> Bool /// Removes a message by ID from whichever public conversation contains it. @discardableResult @@ -109,7 +106,6 @@ extension ChatViewModel: ChatPublicConversationContext { // `geoParticipantCount(for:)`, `isNostrBlocked(pubkeyHexLowercased:)`, // `deriveNostrIdentity(forGeohash:)`, the public conversation store // intents (`appendPublicMessage(_:to:)`, - // `appendGeohashMessageIfAbsent(_:toGeohash:)`, // `publicConversationContainsMessage(withID:in:)`, // `removePublicMessage(withID:)`, // `removePublicMessages(fromGeohash:where:)`, @@ -520,27 +516,27 @@ final class ChatPublicConversationCoordinator: PublicMessagePipelineDelegate { #endif } - func pipeline(_ pipeline: PublicMessagePipeline, normalizeContent content: String) -> String { + func pipeline(_: PublicMessagePipeline, normalizeContent content: String) -> String { context.normalizedContentKey(content) } - func pipeline(_ pipeline: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? { + func pipeline(_: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? { context.contentTimestamp(forKey: key) } - func pipeline(_ pipeline: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) { + func pipeline(_: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) { context.recordContentKey(key, timestamp: timestamp) } - func pipeline(_ pipeline: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool { + func pipeline(_: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool { context.appendPublicMessage(message, to: conversationID) } - func pipelinePrewarmMessage(_ pipeline: PublicMessagePipeline, message: BitchatMessage) { + func pipelinePrewarmMessage(_: PublicMessagePipeline, message: BitchatMessage) { context.prewarmMessageFormatting(message) } - func pipelineSetBatchingState(_ pipeline: PublicMessagePipeline, isBatching: Bool) { + func pipelineSetBatchingState(_: PublicMessagePipeline, isBatching: Bool) { context.setPublicBatching(isBatching) } } diff --git a/bitchat/ViewModels/ChatVerificationCoordinator.swift b/bitchat/ViewModels/ChatVerificationCoordinator.swift index 8f7d484a..3b78797d 100644 --- a/bitchat/ViewModels/ChatVerificationCoordinator.swift +++ b/bitchat/ViewModels/ChatVerificationCoordinator.swift @@ -135,7 +135,6 @@ final class ChatVerificationCoordinator { let noiseKeyHex: String let signKeyHex: String let nonceA: Data - let startedAt: Date var sent: Bool } @@ -258,7 +257,6 @@ final class ChatVerificationCoordinator { noiseKeyHex: qr.noiseKeyHex, signKeyHex: qr.signKeyHex, nonceA: nonce, - startedAt: Date(), sent: false ) pendingQRVerifications[peerID] = pending diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 38985d62..0877090e 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -84,7 +84,6 @@ import SwiftUI import Combine import CommonCrypto import CoreBluetooth -import Tor #if os(iOS) import UIKit #endif @@ -222,12 +221,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele conversations.unreadDirectRoutingPeerIDs() } - /// Check if there are any unread messages (including from temporary Nostr peer IDs) - @MainActor - var hasAnyUnreadMessages: Bool { - !unreadPrivateMessages.isEmpty - } - /// Open the most relevant private chat when tapping the toolbar unread icon. /// Prefers the most recently active unread conversation, otherwise the most recent PM. @MainActor @@ -245,20 +238,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele set { peerIdentityStore.setSelectedPrivateChatFingerprint(newValue) } } - // Resolve full Noise key for a peer's short ID (used by UI header rendering) - @MainActor - private func getNoiseKeyForShortID(_ shortPeerID: PeerID) -> PeerID? { - if let mapped = peerIdentityStore.stablePeerID(forShortID: shortPeerID) { return mapped } - // Fallback: derive from active Noise session if available - if shortPeerID.id.count == 16, - let key = meshService.noiseSessionPublicKeyData(for: shortPeerID) { - let stable = PeerID(hexData: key) - peerIdentityStore.setStablePeerID(stable, forShortID: shortPeerID) - return stable - } - return nil - } - // Resolve short mesh ID (16-hex) from a full Noise public key hex (64-hex) @MainActor func getShortIDForNoiseKey(_ fullNoiseKeyHex: PeerID) -> PeerID { @@ -354,18 +333,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele // MARK: - Social Features (Delegated to PeerStateManager) - @MainActor - var favoritePeers: Set { unifiedPeerService.favoritePeers } @MainActor var blockedUsers: Set { unifiedPeerService.blockedUsers } // MARK: - Encryption and Security - // Noise Protocol encryption status - var peerEncryptionStatus: [PeerID: EncryptionStatus] { - get { peerIdentityStore.encryptionStatuses } - set { peerIdentityStore.replaceEncryptionStatuses(newValue) } - } var verifiedFingerprints: Set { get { peerIdentityStore.verifiedFingerprints } set { peerIdentityStore.setVerifiedFingerprints(newValue) } @@ -959,11 +931,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele // MARK: - Public helpers - /// Published geohash people list for SwiftUI observation - var geohashPeople: [GeoPerson] { - participantTracker.visiblePeople - } - /// Return the current, pruned, sorted people list for the active geohash without mutating state. @MainActor func visibleGeohashPeople() -> [GeoPerson] { @@ -1056,14 +1023,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele publicConversationCoordinator.displayNameForNostrPubkey(pubkeyHex) } - // MARK: - Media Transfers - - private enum MediaSendError: Error { - case encodingFailed - case tooLarge - case copyFailed - } - func currentPublicSender() -> (name: String, peerID: PeerID) { publicConversationCoordinator.currentPublicSender() } @@ -1125,7 +1084,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele } @MainActor - @objc func handlePeerStatusUpdate(_ notification: Notification) { + @objc func handlePeerStatusUpdate(_: Notification) { peerIdentityCoordinator.handlePeerStatusUpdate() } @@ -1159,15 +1118,6 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele lifecycleCoordinator.markPrivateMessagesAsRead(from: peerID) } - func getMessages(for peerID: PeerID?) -> [BitchatMessage] { - lifecycleCoordinator.getMessages(for: peerID) - } - - @MainActor - func getPrivateChatMessages(for peerID: PeerID) -> [BitchatMessage] { - lifecycleCoordinator.getPrivateChatMessages(for: peerID) - } - @MainActor func getPeerIDForNickname(_ nickname: String) -> PeerID? { peerIdentityCoordinator.getPeerIDForNickname(nickname) diff --git a/bitchat/ViewModels/Extensions/ChatViewModel+Nostr.swift b/bitchat/ViewModels/Extensions/ChatViewModel+Nostr.swift index a63d6672..8205e13b 100644 --- a/bitchat/ViewModels/Extensions/ChatViewModel+Nostr.swift +++ b/bitchat/ViewModels/Extensions/ChatViewModel+Nostr.swift @@ -35,11 +35,6 @@ extension ChatViewModel { nostrCoordinator.inbound.handleNostrEvent(event) } - @MainActor - func subscribeToGeoChat(_ ch: GeohashChannel) { - nostrCoordinator.subscriptions.subscribeToGeoChat(ch) - } - @MainActor func handleGiftWrap(_ giftWrap: NostrEvent, id: NostrIdentity) { nostrCoordinator.inbound.handleGiftWrap(giftWrap, id: id) @@ -55,21 +50,11 @@ extension ChatViewModel { nostrCoordinator.subscriptions.beginGeohashSampling(for: geohashes) } - @MainActor - func subscribe(_ gh: String) { - nostrCoordinator.subscriptions.subscribe(gh) - } - @MainActor func subscribeNostrEvent(_ event: NostrEvent, gh: String) { nostrCoordinator.presence.subscribeNostrEvent(event, gh: gh) } - @MainActor - func cooldownPerGeohash(_ gh: String, content: String, event: NostrEvent) { - nostrCoordinator.presence.cooldownPerGeohash(gh, content: content, event: event) - } - @MainActor func endGeohashSampling() { nostrCoordinator.subscriptions.endGeohashSampling() @@ -80,35 +65,11 @@ extension ChatViewModel { nostrCoordinator.subscriptions.setupNostrMessageHandling() } - @MainActor - func handleNostrMessage(_ giftWrap: NostrEvent) { - nostrCoordinator.inbound.handleNostrMessage(giftWrap) - } - - func processNostrMessage(_ giftWrap: NostrEvent) async { - await nostrCoordinator.inbound.processNostrMessage(giftWrap) - } - @MainActor func findNoiseKey(for nostrPubkey: String) -> Data? { nostrCoordinator.inbound.findNoiseKey(for: nostrPubkey) } - @MainActor - func sendDeliveryAckViaNostrEmbedded( - _ message: BitchatMessage, - wasReadBefore: Bool, - senderPubkey: String, - key: Data? - ) { - nostrCoordinator.sendDeliveryAckViaNostrEmbedded( - message, - wasReadBefore: wasReadBefore, - senderPubkey: senderPubkey, - key: key - ) - } - @MainActor func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) { nostrCoordinator.sendFavoriteNotificationViaNostr(noisePublicKey: noisePublicKey, isFavorite: isFavorite) diff --git a/bitchat/ViewModels/Extensions/ChatViewModel+PrivateChat.swift b/bitchat/ViewModels/Extensions/ChatViewModel+PrivateChat.swift index 2d40cfea..d77a4221 100644 --- a/bitchat/ViewModels/Extensions/ChatViewModel+PrivateChat.swift +++ b/bitchat/ViewModels/Extensions/ChatViewModel+PrivateChat.swift @@ -55,16 +55,6 @@ extension ChatViewModel { privateConversationCoordinator.handleReadReceipt(payload, senderPubkey: senderPubkey, convKey: convKey) } - @MainActor - func sendDeliveryAckIfNeeded(to messageId: String, senderPubKey: String, from id: NostrIdentity) { - privateConversationCoordinator.sendDeliveryAckIfNeeded(to: messageId, senderPubKey: senderPubKey, from: id) - } - - @MainActor - func sendReadReceiptIfNeeded(to messageId: String, senderPubKey: String, from id: NostrIdentity) { - privateConversationCoordinator.sendReadReceiptIfNeeded(to: messageId, senderPubKey: senderPubKey, from: id) - } - @MainActor func sendVoiceNote(at url: URL) { mediaTransferCoordinator.sendVoiceNote(at: url) @@ -162,11 +152,6 @@ extension ChatViewModel { mediaTransferCoordinator.clearTransferMapping(for: messageID) } - @MainActor - func handleMediaSendFailure(messageID: String, reason: String) { - mediaTransferCoordinator.handleMediaSendFailure(messageID: messageID, reason: reason) - } - @MainActor func handleTransferEvent(_ event: TransferProgressManager.Event) { mediaTransferCoordinator.handleTransferEvent(event) @@ -194,59 +179,6 @@ extension ChatViewModel { privateConversationCoordinator.handlePrivateMessage(message) } - @MainActor - func isDuplicateMessage(_ messageId: String, targetPeerID: PeerID) -> Bool { - privateConversationCoordinator.isDuplicateMessage(messageId, targetPeerID: targetPeerID) - } - - @MainActor - func addMessageToPrivateChatsIfNeeded(_ message: BitchatMessage, targetPeerID: PeerID) { - privateConversationCoordinator.addMessageToPrivateChatsIfNeeded(message, targetPeerID: targetPeerID) - } - - @MainActor - func mirrorToEphemeralIfNeeded(_ message: BitchatMessage, targetPeerID: PeerID, key: Data?) { - privateConversationCoordinator.mirrorToEphemeralIfNeeded(message, targetPeerID: targetPeerID, key: key) - } - - @MainActor - func handleViewingThisChat(_ message: BitchatMessage, targetPeerID: PeerID, key: Data?, senderPubkey: String) { - privateConversationCoordinator.handleViewingThisChat( - message, - targetPeerID: targetPeerID, - key: key, - senderPubkey: senderPubkey - ) - } - - @MainActor - func markAsUnreadIfNeeded( - shouldMarkAsUnread: Bool, - targetPeerID: PeerID, - key: Data?, - isRecentMessage: Bool, - senderNickname: String, - messageContent: String - ) { - privateConversationCoordinator.markAsUnreadIfNeeded( - shouldMarkAsUnread: shouldMarkAsUnread, - targetPeerID: targetPeerID, - key: key, - isRecentMessage: isRecentMessage, - senderNickname: senderNickname, - messageContent: messageContent - ) - } - - @MainActor - func handleFavoriteNotification(_ content: String, from peerID: PeerID, senderNickname: String) { - privateConversationCoordinator.handleFavoriteNotification( - content, - from: peerID, - senderNickname: senderNickname - ) - } - @MainActor func processActionMessage(_ message: BitchatMessage) -> BitchatMessage { privateConversationCoordinator.processActionMessage(message) @@ -257,11 +189,6 @@ extension ChatViewModel { privateConversationCoordinator.migratePrivateChatsIfNeeded(for: peerID, senderNickname: senderNickname) } - @MainActor - func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) { - privateConversationCoordinator.sendFavoriteNotification(to: peerID, isFavorite: isFavorite) - } - @MainActor func isMessageBlocked(_ message: BitchatMessage) -> Bool { privateConversationCoordinator.isMessageBlocked(message) diff --git a/bitchat/ViewModels/Extensions/ChatViewModel+Tor.swift b/bitchat/ViewModels/Extensions/ChatViewModel+Tor.swift index 443849db..a0142cde 100644 --- a/bitchat/ViewModels/Extensions/ChatViewModel+Tor.swift +++ b/bitchat/ViewModels/Extensions/ChatViewModel+Tor.swift @@ -54,7 +54,7 @@ extension ChatViewModel { } } - @objc func handleTorPreferenceChanged(_ notification: Notification) { + @objc func handleTorPreferenceChanged(_: Notification) { Task { @MainActor in self.torStatusAnnounced = false self.torInitialReadyAnnounced = false diff --git a/bitchat/ViewModels/MessageRateLimiter.swift b/bitchat/ViewModels/MessageRateLimiter.swift index a0309447..1196c47f 100644 --- a/bitchat/ViewModels/MessageRateLimiter.swift +++ b/bitchat/ViewModels/MessageRateLimiter.swift @@ -79,9 +79,4 @@ struct MessageRateLimiter { return senderAllowed && contentAllowed } - - mutating func reset() { - senderBuckets.removeAll() - contentBuckets.removeAll() - } } diff --git a/bitchat/ViewModels/MinimalDistancePalette.swift b/bitchat/ViewModels/MinimalDistancePalette.swift index fb05d3c9..c142382c 100644 --- a/bitchat/ViewModels/MinimalDistancePalette.swift +++ b/bitchat/ViewModels/MinimalDistancePalette.swift @@ -82,13 +82,6 @@ final class MinimalDistancePalette { return Color(hue: entry.hue, saturation: saturation, brightness: brightness) } - @MainActor - func reset() { - currentSeeds.removeAll() - entries.removeAll() - previousEntries.removeAll() - } - @MainActor private func rebuildEntries() { guard !currentSeeds.isEmpty else { diff --git a/bitchat/ViewModels/PublicMessagePipeline.swift b/bitchat/ViewModels/PublicMessagePipeline.swift index 7383151b..289e1d75 100644 --- a/bitchat/ViewModels/PublicMessagePipeline.swift +++ b/bitchat/ViewModels/PublicMessagePipeline.swift @@ -14,15 +14,15 @@ import Foundation @MainActor protocol PublicMessagePipelineDelegate: AnyObject { - func pipeline(_ pipeline: PublicMessagePipeline, normalizeContent content: String) -> String - func pipeline(_ pipeline: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? - func pipeline(_ pipeline: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) + func pipeline(_: PublicMessagePipeline, normalizeContent content: String) -> String + func pipeline(_: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? + func pipeline(_: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) /// Commits a batched message to its conversation in the store. /// Returns `false` when the message was already present (ID dedup). @discardableResult - func pipeline(_ pipeline: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool - func pipelinePrewarmMessage(_ pipeline: PublicMessagePipeline, message: BitchatMessage) - func pipelineSetBatchingState(_ pipeline: PublicMessagePipeline, isBatching: Bool) + func pipeline(_: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool + func pipelinePrewarmMessage(_: PublicMessagePipeline, message: BitchatMessage) + func pipelineSetBatchingState(_: PublicMessagePipeline, isBatching: Bool) } @MainActor diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index 1b696234..8335d874 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -15,8 +15,6 @@ struct AppInfoView: View { AppTheme(rawValue: appThemeRawValue) ?? .matrix } - private var backgroundColor: Color { palette.background } - private var textColor: Color { palette.primary } private var secondaryTextColor: Color { palette.secondary } diff --git a/bitchat/Views/ContentSheetViews.swift b/bitchat/Views/ContentSheetViews.swift index f8fb9411..a6a68c82 100644 --- a/bitchat/Views/ContentSheetViews.swift +++ b/bitchat/Views/ContentSheetViews.swift @@ -11,8 +11,6 @@ struct ContentPeopleSheetView: View { @EnvironmentObject private var privateConversationModel: PrivateConversationModel @EnvironmentObject private var verificationModel: VerificationModel @EnvironmentObject private var conversationUIModel: ConversationUIModel - @EnvironmentObject private var locationChannelsModel: LocationChannelsModel - @EnvironmentObject private var peerListModel: PeerListModel @Binding var showSidebar: Bool @Binding var messageText: String @@ -79,8 +77,7 @@ struct ContentPeopleSheetView: View { #endif } else { ContentPeopleListView( - showSidebar: $showSidebar, - headerHeight: headerHeight + showSidebar: $showSidebar ) } } @@ -142,8 +139,6 @@ private struct ContentPeopleListView: View { @Binding var showSidebar: Bool - let headerHeight: CGFloat - @State private var showVerifySheet = false var body: some View { @@ -285,7 +280,6 @@ private extension ContentPeopleListView { } private struct ContentPrivateChatSheetView: View { - @EnvironmentObject private var appChromeModel: AppChromeModel @EnvironmentObject private var privateConversationModel: PrivateConversationModel @Binding var showSidebar: Bool diff --git a/bitchat/Views/FingerprintView.swift b/bitchat/Views/FingerprintView.swift index fb4541f5..a6c371ae 100644 --- a/bitchat/Views/FingerprintView.swift +++ b/bitchat/Views/FingerprintView.swift @@ -17,8 +17,6 @@ struct FingerprintView: View { private var textColor: Color { palette.primary } - private var backgroundColor: Color { palette.background } - private enum Strings { static let title: LocalizedStringKey = "fingerprint.title" static let theirFingerprint: LocalizedStringKey = "fingerprint.their_label" @@ -45,9 +43,6 @@ struct FingerprintView: View { count ) } - static func unknownPeer() -> String { - String(localized: "common.unknown", comment: "Label for an unknown peer") - } } var body: some View { diff --git a/bitchat/Views/LocationChannelsSheet.swift b/bitchat/Views/LocationChannelsSheet.swift index 0a6e077a..c8f3241b 100644 --- a/bitchat/Views/LocationChannelsSheet.swift +++ b/bitchat/Views/LocationChannelsSheet.swift @@ -13,8 +13,6 @@ struct LocationChannelsSheet: View { @State private var customGeohash: String = "" @State private var customError: String? = nil - private var backgroundColor: Color { palette.background } - private enum Strings { static let title: LocalizedStringKey = "location_channels.title" static let description: LocalizedStringKey = "location_channels.description" @@ -401,7 +399,7 @@ struct LocationChannelsSheet: View { title: String, subtitlePrefix: String, subtitleName: String? = nil, - subtitleNameBold: Bool = false, + subtitleNameBold _: Bool = false, isSelected: Bool, titleColor: Color? = nil, titleBold: Bool = false, diff --git a/bitchat/Views/VerificationViews.swift b/bitchat/Views/VerificationViews.swift index 1fba4f2d..94aaf211 100644 --- a/bitchat/Views/VerificationViews.swift +++ b/bitchat/Views/VerificationViews.swift @@ -288,7 +288,6 @@ struct VerificationSheetView: View { @State private var showingScanner = false @ThemedPalette private var palette - private var backgroundColor: Color { palette.background } private var accentColor: Color { palette.accent } private var boxColor: Color { palette.secondary.opacity(0.1) } diff --git a/bitchatTests/ChatNostrCoordinatorContextTests.swift b/bitchatTests/ChatNostrCoordinatorContextTests.swift index 5cbd0c7d..647e0957 100644 --- a/bitchatTests/ChatNostrCoordinatorContextTests.swift +++ b/bitchatTests/ChatNostrCoordinatorContextTests.swift @@ -70,7 +70,6 @@ private final class MockChatNostrContext: ChatNostrContext { private(set) var mentionCheckedMessageIDs: [String] = [] private(set) var hapticMessageIDs: [String] = [] - func handlePublicMessage(_ message: BitchatMessage) { handledPublicMessages.append(message) } func handlePublicMessage(_ message: BitchatMessage, powBits: Int) { handledPublicMessages.append(message) } func checkForMentions(_ message: BitchatMessage) { mentionCheckedMessageIDs.append(message.id) } func sendHapticFeedback(for message: BitchatMessage) { hapticMessageIDs.append(message.id) } @@ -214,8 +213,6 @@ private final class MockChatNostrContext: ChatNostrContext { // Favorites & notifications var favoriteRelationshipsByNoiseKey: [Data: FavoritesPersistenceService.FavoriteRelationship] = [:] - private(set) var addedFavorites: [(noiseKey: Data, nostrPublicKey: String?, nickname: String)] = [] - private(set) var postedLocalNotifications: [(title: String, body: String, identifier: String)] = [] private(set) var geohashActivityNotifications: [(geohash: String, bodyPreview: String)] = [] func favoriteRelationship(forNoiseKey noiseKey: Data) -> FavoritesPersistenceService.FavoriteRelationship? { @@ -226,14 +223,6 @@ private final class MockChatNostrContext: ChatNostrContext { Array(favoriteRelationshipsByNoiseKey.values) } - func addFavorite(noiseKey: Data, nostrPublicKey: String?, nickname: String) { - addedFavorites.append((noiseKey, nostrPublicKey, nickname)) - } - - func postLocalNotification(title: String, body: String, identifier: String) { - postedLocalNotifications.append((title, body, identifier)) - } - func notifyGeohashActivity(geohash: String, bodyPreview: String) { geohashActivityNotifications.append((geohash, bodyPreview)) } @@ -250,24 +239,6 @@ private func drainMainQueue() async { } } -private func makeFavoriteRelationship( - noiseKey: Data, - nostrPublicKey: String? = nil, - nickname: String = "alice", - isFavorite: Bool = false, - theyFavoritedUs: Bool = false -) -> FavoritesPersistenceService.FavoriteRelationship { - FavoritesPersistenceService.FavoriteRelationship( - peerNoisePublicKey: noiseKey, - peerNostrPublicKey: nostrPublicKey, - peerNickname: nickname, - isFavorite: isFavorite, - theyFavoritedUs: theyFavoritedUs, - favoritedAt: Date(timeIntervalSince1970: 0), - lastUpdated: Date(timeIntervalSince1970: 0) - ) -} - // MARK: - Coordinator Tests Against Mock Context /// Exercises `ChatNostrCoordinator` against `MockChatNostrContext` with no diff --git a/bitchatTests/ChatPeerIdentityCoordinatorContextTests.swift b/bitchatTests/ChatPeerIdentityCoordinatorContextTests.swift index b756999b..bbdda609 100644 --- a/bitchatTests/ChatPeerIdentityCoordinatorContextTests.swift +++ b/bitchatTests/ChatPeerIdentityCoordinatorContextTests.swift @@ -29,7 +29,6 @@ private final class MockChatPeerIdentityContext: ChatPeerIdentityContext { var unreadPrivateMessages: Set = [] var selectedPrivateChatPeer: PeerID? var selectedPrivateChatFingerprint: String? - var nickname = "me" var myPeerID = PeerID(str: "0011223344556677") var activeChannel: ChannelID = .mesh private(set) var notifyUIChangedCount = 0 diff --git a/bitchatTests/ChatPrivateConversationCoordinatorContextTests.swift b/bitchatTests/ChatPrivateConversationCoordinatorContextTests.swift index 9e96cabe..aed8f482 100644 --- a/bitchatTests/ChatPrivateConversationCoordinatorContextTests.swift +++ b/bitchatTests/ChatPrivateConversationCoordinatorContextTests.swift @@ -100,11 +100,6 @@ private final class MockChatPrivateConversationContext: ChatPrivateConversationC unreadPrivateMessages.remove(peerID) } - func removePrivateChat(_ peerID: PeerID) { - privateChats.removeValue(forKey: peerID) - unreadPrivateMessages.remove(peerID) - } - func migratePrivateChat(from oldPeerID: PeerID, to newPeerID: PeerID) { migratedChats.append((oldPeerID, newPeerID)) guard oldPeerID != newPeerID, let source = privateChats[oldPeerID] else { return } @@ -177,12 +172,10 @@ private final class MockChatPrivateConversationContext: ChatPrivateConversationC // Routing & acknowledgements private(set) var routedPrivateMessages: [(content: String, peerID: PeerID, messageID: String)] = [] private(set) var routedReadReceipts: [(messageID: String, peerID: PeerID)] = [] - private(set) var routedFavoriteNotifications: [(peerID: PeerID, isFavorite: Bool)] = [] private(set) var meshReadReceipts: [(messageID: String, peerID: PeerID)] = [] private(set) var geoPrivateMessages: [(content: String, recipientHex: String, messageID: String)] = [] private(set) var geoDeliveryAcks: [(messageID: String, recipientHex: String)] = [] private(set) var geoReadReceipts: [(messageID: String, recipientHex: String)] = [] - private(set) var embeddedDeliveryAckMessageIDs: [String] = [] func routePrivateMessage(_ content: String, to peerID: PeerID, recipientNickname: String, messageID: String) { routedPrivateMessages.append((content, peerID, messageID)) @@ -192,10 +185,6 @@ private final class MockChatPrivateConversationContext: ChatPrivateConversationC routedReadReceipts.append((receipt.originalMessageID, peerID)) } - func routeFavoriteNotification(to peerID: PeerID, isFavorite: Bool) { - routedFavoriteNotifications.append((peerID, isFavorite)) - } - func sendMeshReadReceipt(_ receipt: ReadReceipt, to peerID: PeerID) { meshReadReceipts.append((receipt.originalMessageID, peerID)) } @@ -212,10 +201,6 @@ private final class MockChatPrivateConversationContext: ChatPrivateConversationC geoReadReceipts.append((messageID, recipientHex)) } - func sendDeliveryAckViaNostrEmbedded(_ message: BitchatMessage, wasReadBefore: Bool, senderPubkey: String, key: Data?) { - embeddedDeliveryAckMessageIDs.append(message.id) - } - // Favorites & notifications var favoriteRelationshipsByNoiseKey: [Data: FavoritesPersistenceService.FavoriteRelationship] = [:] private(set) var peerFavoritedUsUpdates: [(noiseKey: Data, favorited: Bool, nickname: String, nostrPublicKey: String?)] = [] diff --git a/bitchatTests/ChatPublicConversationCoordinatorContextTests.swift b/bitchatTests/ChatPublicConversationCoordinatorContextTests.swift index be76fe82..54858217 100644 --- a/bitchatTests/ChatPublicConversationCoordinatorContextTests.swift +++ b/bitchatTests/ChatPublicConversationCoordinatorContextTests.swift @@ -58,11 +58,6 @@ private final class MockChatPublicConversationContext: ChatPublicConversationCon return true } - @discardableResult - func appendGeohashMessageIfAbsent(_ message: BitchatMessage, toGeohash geohash: String) -> Bool { - appendPublicMessage(message, to: .geohash(geohash.lowercased())) - } - func publicConversationContainsMessage(withID messageID: String, in conversationID: ConversationID) -> Bool { conversations[conversationID]?.contains(where: { $0.id == messageID }) == true } diff --git a/bitchatTests/CommandProcessorTests.swift b/bitchatTests/CommandProcessorTests.swift index 3da82140..e2d9d561 100644 --- a/bitchatTests/CommandProcessorTests.swift +++ b/bitchatTests/CommandProcessorTests.swift @@ -675,10 +675,6 @@ private final class MockCommandContextProvider: CommandContextProvider { toggledFavorites.append(peerID) } - func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) { - favoriteNotifications.append((peerID, isFavorite)) - } - // Groups: record the parsed subcommand + argument the processor forwarded. private(set) var groupCommands: [(subcommand: String, argument: String)] = [] diff --git a/bitchatTests/EndToEnd/CourierEndToEndTests.swift b/bitchatTests/EndToEnd/CourierEndToEndTests.swift index 22e56c79..cedcc15f 100644 --- a/bitchatTests/EndToEnd/CourierEndToEndTests.swift +++ b/bitchatTests/EndToEnd/CourierEndToEndTests.swift @@ -590,9 +590,6 @@ private final class CourierCaptureTransport: Transport { private(set) var courierSends: [(messageID: String, recipientKey: Data, couriers: [PeerID])] = [] private(set) var directSends: [String] = [] - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { - Just(snapshots).eraseToAnyPublisher() - } func currentPeerSnapshots() -> [TransportPeerSnapshot] { snapshots } var myPeerID = PeerID(str: "00000000000000aa") diff --git a/bitchatTests/EndToEnd/PublicChatE2ETests.swift b/bitchatTests/EndToEnd/PublicChatE2ETests.swift index 6672e678..b67901d2 100644 --- a/bitchatTests/EndToEnd/PublicChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PublicChatE2ETests.swift @@ -18,9 +18,7 @@ struct PublicChatE2ETests { private let charlie: MockBLEService private let david: MockBLEService private let bus = MockBLEBus() - - private var receivedMessages: [String: [BitchatMessage]] = [:] - + init() { // Create mock services with unique peer IDs to avoid any collision alice = MockBLEService(peerID: PeerID(str: UUID().uuidString), nickname: TestConstants.testNickname1, bus: bus) diff --git a/bitchatTests/FontBitchatTests.swift b/bitchatTests/FontBitchatTests.swift index 729c2bf8..327c5efb 100644 --- a/bitchatTests/FontBitchatTests.swift +++ b/bitchatTests/FontBitchatTests.swift @@ -1,6 +1,5 @@ import SwiftUI import XCTest -@testable import bitchat final class FontBitchatTests: XCTestCase { // func testMonospacedMapping() { diff --git a/bitchatTests/Fragmentation/FragmentationTests.swift b/bitchatTests/Fragmentation/FragmentationTests.swift index 1052b012..5b64bfeb 100644 --- a/bitchatTests/Fragmentation/FragmentationTests.swift +++ b/bitchatTests/Fragmentation/FragmentationTests.swift @@ -369,7 +369,6 @@ extension FragmentationTests { func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {} func didReceiveNoisePayload(from peerID: PeerID, type: NoisePayloadType, payload: Data, timestamp: Date) {} func didUpdateBluetoothState(_ state: CBManagerState) {} - func didReceiveRegionalPublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date) {} } // Helper: build a large message packet (unencrypted public message) diff --git a/bitchatTests/Integration/TestNetworkHelper.swift b/bitchatTests/Integration/TestNetworkHelper.swift index d7b7b7ef..d9e0b3a9 100644 --- a/bitchatTests/Integration/TestNetworkHelper.swift +++ b/bitchatTests/Integration/TestNetworkHelper.swift @@ -33,14 +33,6 @@ final class TestNetworkHelper { return node } - func getNode(_ name: String) -> MockBLEService? { - nodes[name] - } - - func getManager(_ name: String) -> NoiseSessionManager? { - noiseManagers[name] - } - // MARK: - Topology func connect(_ a: String, _ b: String) { diff --git a/bitchatTests/LocalizationCoverageTests.swift b/bitchatTests/LocalizationCoverageTests.swift index 10e0fb87..c9d7c602 100644 --- a/bitchatTests/LocalizationCoverageTests.swift +++ b/bitchatTests/LocalizationCoverageTests.swift @@ -10,7 +10,6 @@ struct LocalizationCoverageTests { .deletingLastPathComponent() // repo root private struct Catalog { - let sourceLanguage: String /// key -> set of locales with a localization entry let coverage: [String: Set] /// all locales appearing anywhere in the catalog @@ -22,7 +21,6 @@ struct LocalizationCoverageTests { let data = try Data(contentsOf: url) let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any]) let strings = try #require(root["strings"] as? [String: Any]) - let sourceLanguage = try #require(root["sourceLanguage"] as? String) var coverage: [String: Set] = [:] for (key, value) in strings { @@ -43,7 +41,7 @@ struct LocalizationCoverageTests { } coverage[key] = locales } - return Catalog(sourceLanguage: sourceLanguage, coverage: coverage) + return Catalog(coverage: coverage) } @Test func mainCatalogCoversAllLocalesForEveryKey() throws { diff --git a/bitchatTests/Mocks/MockBLEBus.swift b/bitchatTests/Mocks/MockBLEBus.swift index c4b5cb24..7496d8db 100644 --- a/bitchatTests/Mocks/MockBLEBus.swift +++ b/bitchatTests/Mocks/MockBLEBus.swift @@ -8,7 +8,6 @@ import Foundation import BitFoundation -@testable import bitchat final class MockBLEBus { private var registry: [PeerID: MockBLEService] = [:] diff --git a/bitchatTests/Mocks/MockBLEService.swift b/bitchatTests/Mocks/MockBLEService.swift index 7437a8be..a127b3dd 100644 --- a/bitchatTests/Mocks/MockBLEService.swift +++ b/bitchatTests/Mocks/MockBLEService.swift @@ -48,10 +48,6 @@ final class MockBLEService: NSObject { set { myNickname = newValue } } - var nickname: String { - return myNickname - } - var peerID: PeerID { return myPeerID } @@ -62,12 +58,6 @@ final class MockBLEService: NSObject { self.bus = bus } - // MARK: - Methods matching BLEService - - func setNickname(_ nickname: String) { - self.myNickname = nickname - } - // MARK: - In-memory test bus (for E2E/Integration) /// Registers this instance on first use. @@ -92,10 +82,6 @@ final class MockBLEService: NSObject { return connectedPeers.contains(peerID) } - func peerNickname(peerID: String) -> String? { - "MockPeer_\(peerID)" - } - func getPeerNicknames() -> [PeerID: String] { var nicknames: [PeerID: String] = [:] for peer in connectedPeers { @@ -103,10 +89,6 @@ final class MockBLEService: NSObject { } return nicknames } - - func getPeers() -> [PeerID: String] { - return getPeerNicknames() - } /// Keep local echo synchronous so Swift Testing confirmations observe it deterministically. private func deliverLocalEcho(_ message: BitchatMessage) { @@ -155,14 +137,6 @@ final class MockBLEService: NSObject { } } - func sendFileBroadcast(_ packet: BitchatFilePacket, transferId: String) { - // Tests currently ignore file transfer flows; keep stub for protocol conformance. - } - - func sendFilePrivate(_ packet: BitchatFilePacket, to peerID: PeerID, transferId: String) { - // Tests currently ignore file transfer flows; keep stub for protocol conformance. - } - func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String) { let message = BitchatMessage( id: messageID, @@ -209,39 +183,6 @@ final class MockBLEService: NSObject { } } - 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 getFingerprint(for peerID: String) -> String? { - return nil - } - // MARK: - Test Helper Methods func simulateConnectedPeer(_ peerID: PeerID) { @@ -314,9 +255,6 @@ final class MockBLEService: NSObject { } } -// Backward compatibility for older tests -typealias MockSimplifiedBluetoothService = MockBLEService - // MARK: - Helpers extension MockBLEService { diff --git a/bitchatTests/Mocks/MockIdentityManager.swift b/bitchatTests/Mocks/MockIdentityManager.swift index 1ff1926a..a3603017 100644 --- a/bitchatTests/Mocks/MockIdentityManager.swift +++ b/bitchatTests/Mocks/MockIdentityManager.swift @@ -11,18 +11,11 @@ import BitFoundation @testable import bitchat final class MockIdentityManager: SecureIdentityStateManagerProtocol { - private let keychain: KeychainManagerProtocol private var blockedFingerprints: Set = [] private var blockedNostrPubkeys: Set = [] private var socialIdentities: [String: SocialIdentity] = [:] - - init(_ keychain: KeychainManagerProtocol) { - self.keychain = keychain - } - - func loadIdentityCache() {} - - func saveIdentityCache() {} + + init(_: KeychainManagerProtocol) {} func forceSave() {} @@ -45,12 +38,6 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol { } } - func getFavorites() -> Set { - Set() - } - - func setFavorite(_ fingerprint: String, isFavorite: Bool) {} - func isFavorite(fingerprint: String) -> Bool { false } @@ -99,9 +86,7 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol { } func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) {} - - func updateHandshakeState(peerID: PeerID, state: HandshakeState) {} - + func clearAllIdentityData() {} func removeEphemeralSession(peerID: PeerID) {} @@ -139,10 +124,6 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol { !(vouchesByVouchee[fingerprint] ?? []).isEmpty } - func effectiveTrustLevel(for fingerprint: String) -> TrustLevel { - socialIdentities[fingerprint]?.trustLevel ?? .unknown - } - func lastVouchBatchSent(to fingerprint: String) -> Date? { vouchBatchSentAt[fingerprint] } diff --git a/bitchatTests/Mocks/MockTransport.swift b/bitchatTests/Mocks/MockTransport.swift index 5f1243e8..6c617249 100644 --- a/bitchatTests/Mocks/MockTransport.swift +++ b/bitchatTests/Mocks/MockTransport.swift @@ -26,9 +26,6 @@ final class MockTransport: Transport { var myNickname: String = "TestUser" private let peerSnapshotSubject = CurrentValueSubject<[TransportPeerSnapshot], Never>([]) - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { - peerSnapshotSubject.eraseToAnyPublisher() - } // MARK: - Recording Properties (for test assertions) diff --git a/bitchatTests/Noise/NoiseProtocolTests.swift b/bitchatTests/Noise/NoiseProtocolTests.swift index 5d1a3129..f7e8e6be 100644 --- a/bitchatTests/Noise/NoiseProtocolTests.swift +++ b/bitchatTests/Noise/NoiseProtocolTests.swift @@ -31,11 +31,8 @@ struct NoiseTestVector: Codable { let init_prologue: String let init_static: String let init_ephemeral: String - let init_psks: [String]? - let resp_prologue: String let resp_static: String let resp_ephemeral: String - let resp_psks: [String]? let handshake_hash: String? let messages: [TestMessage] diff --git a/bitchatTests/NostrProtocolTests.swift b/bitchatTests/NostrProtocolTests.swift index d237f5fc..1f9d21ab 100644 --- a/bitchatTests/NostrProtocolTests.swift +++ b/bitchatTests/NostrProtocolTests.swift @@ -156,6 +156,7 @@ struct NostrProtocolTests { } } + @Test func testAckRoundTripNIP44V2_Delivered() throws { // Identities let sender = try NostrIdentity.generate() diff --git a/bitchatTests/NotificationStreamAssemblerTests.swift b/bitchatTests/NotificationStreamAssemblerTests.swift index de76bda0..81bf8502 100644 --- a/bitchatTests/NotificationStreamAssemblerTests.swift +++ b/bitchatTests/NotificationStreamAssemblerTests.swift @@ -118,6 +118,7 @@ struct NotificationStreamAssemblerTests { #expect(decoded.timestamp == packet2.timestamp) } + @Test func testAssemblesCompressedLargeFrame() throws { var assembler = NotificationStreamAssembler() diff --git a/bitchatTests/Performance/PerformanceBaselineTests.swift b/bitchatTests/Performance/PerformanceBaselineTests.swift index d496529c..74246df2 100644 --- a/bitchatTests/Performance/PerformanceBaselineTests.swift +++ b/bitchatTests/Performance/PerformanceBaselineTests.swift @@ -610,7 +610,6 @@ private final class PerfNostrContext: ChatNostrContext { func appendGeohashMessageIfAbsent(_ message: BitchatMessage, toGeohash geohash: String) -> Bool { true } private(set) var handledPublicMessageCount = 0 - func handlePublicMessage(_ message: BitchatMessage) { handledPublicMessageCount += 1 } func handlePublicMessage(_ message: BitchatMessage, powBits: Int) { handledPublicMessageCount += 1 } func checkForMentions(_ message: BitchatMessage) {} func sendHapticFeedback(for message: BitchatMessage) {} @@ -668,8 +667,6 @@ private final class PerfNostrContext: ChatNostrContext { func favoriteRelationship(forNoiseKey noiseKey: Data) -> FavoritesPersistenceService.FavoriteRelationship? { nil } func allFavoriteRelationships() -> [FavoritesPersistenceService.FavoriteRelationship] { [] } - func addFavorite(noiseKey: Data, nostrPublicKey: String?, nickname: String) {} - func postLocalNotification(title: String, body: String, identifier: String) {} func notifyGeohashActivity(geohash: String, bodyPreview: String) {} } @@ -779,7 +776,6 @@ private final class PerfDeliveryContext: ChatDeliveryContext { @MainActor private final class PerfPipelineFixture { let viewModel: ChatViewModel - let transport: MockTransport let conversations: ConversationStore let privateInbox: PrivateInboxModel let publicChat: PublicChatModel @@ -791,7 +787,6 @@ private final class PerfPipelineFixture { let transport = MockTransport() let conversations = ConversationStore() - self.transport = transport self.conversations = conversations self.viewModel = ChatViewModel( keychain: keychain, diff --git a/bitchatTests/ProtocolContractTests.swift b/bitchatTests/ProtocolContractTests.swift index e7097121..73609823 100644 --- a/bitchatTests/ProtocolContractTests.swift +++ b/bitchatTests/ProtocolContractTests.swift @@ -23,10 +23,6 @@ private final class DefaultTransportProbe: Transport { var myNickname = "Tester" private(set) var sentMessages: [(content: String, mentions: [String])] = [] - var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> { - subject.eraseToAnyPublisher() - } - func currentPeerSnapshots() -> [TransportPeerSnapshot] { subject.value } func setNickname(_ nickname: String) { myNickname = nickname } func startServices() {} diff --git a/bitchatTests/PublicMessagePipelineTests.swift b/bitchatTests/PublicMessagePipelineTests.swift index 6d1458da..da26d80e 100644 --- a/bitchatTests/PublicMessagePipelineTests.swift +++ b/bitchatTests/PublicMessagePipelineTests.swift @@ -27,28 +27,28 @@ private final class TestPipelineDelegate: PublicMessagePipelineDelegate { committed.filter { $0.conversationID == conversationID }.map(\.message) } - func pipeline(_ pipeline: PublicMessagePipeline, normalizeContent content: String) -> String { + func pipeline(_: PublicMessagePipeline, normalizeContent content: String) -> String { dedupService.normalizedContentKey(content) } - func pipeline(_ pipeline: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? { + func pipeline(_: PublicMessagePipeline, contentTimestampForKey key: String) -> Date? { dedupService.contentTimestamp(forKey: key) } - func pipeline(_ pipeline: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) { + func pipeline(_: PublicMessagePipeline, recordContentKey key: String, timestamp: Date) { dedupService.recordContentKey(key, timestamp: timestamp) recordedContentKeys.append(key) } - func pipeline(_ pipeline: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool { + func pipeline(_: PublicMessagePipeline, commit message: BitchatMessage, to conversationID: ConversationID) -> Bool { guard !rejectedMessageIDs.contains(message.id) else { return false } committed.append((message, conversationID)) return true } - func pipelinePrewarmMessage(_ pipeline: PublicMessagePipeline, message: BitchatMessage) {} + func pipelinePrewarmMessage(_: PublicMessagePipeline, message: BitchatMessage) {} - func pipelineSetBatchingState(_ pipeline: PublicMessagePipeline, isBatching: Bool) { + func pipelineSetBatchingState(_: PublicMessagePipeline, isBatching: Bool) { batchingStates.append(isBatching) } } diff --git a/bitchatTests/Services/GeohashPresenceServiceTests.swift b/bitchatTests/Services/GeohashPresenceServiceTests.swift index f6be1f25..65c33f0c 100644 --- a/bitchatTests/Services/GeohashPresenceServiceTests.swift +++ b/bitchatTests/Services/GeohashPresenceServiceTests.swift @@ -230,8 +230,4 @@ private final class MockGeohashPresenceTimer: GeohashPresenceTimerProtocol { invalidateCallCount += 1 isValid = false } - - func fire() { - handler() - } } diff --git a/bitchatTests/Services/NetworkActivationServiceTests.swift b/bitchatTests/Services/NetworkActivationServiceTests.swift index 01d8fdca..72d9f6be 100644 --- a/bitchatTests/Services/NetworkActivationServiceTests.swift +++ b/bitchatTests/Services/NetworkActivationServiceTests.swift @@ -120,7 +120,6 @@ final class NetworkActivationServiceTests: XCTestCase { return NetworkActivationTestContext( service: service, storage: storage, - permissionSubject: permissionSubject, favoritesSubject: favoritesSubject, torController: torController, relayController: relayController, @@ -148,7 +147,6 @@ final class NetworkActivationServiceTests: XCTestCase { private struct NetworkActivationTestContext { let service: NetworkActivationService let storage: UserDefaults - let permissionSubject: CurrentValueSubject let favoritesSubject: CurrentValueSubject, Never> let torController: MockNetworkActivationTorController let relayController: MockNetworkActivationRelayController diff --git a/bitchatTests/Services/NostrRelayManagerTests.swift b/bitchatTests/Services/NostrRelayManagerTests.swift index fbb46060..125b252c 100644 --- a/bitchatTests/Services/NostrRelayManagerTests.swift +++ b/bitchatTests/Services/NostrRelayManagerTests.swift @@ -1483,7 +1483,6 @@ final class NostrRelayManagerTests: XCTestCase { return RelayManagerTestContext( manager: manager, permissionSubject: permissionSubject, - favoritesSubject: favoritesSubject, sessionFactory: sessionFactory, scheduler: scheduler, clock: clock, @@ -1537,7 +1536,6 @@ final class NostrRelayManagerTests: XCTestCase { private struct RelayManagerTestContext { let manager: NostrRelayManager let permissionSubject: CurrentValueSubject - let favoritesSubject: CurrentValueSubject, Never> let sessionFactory: MockRelaySessionFactory let scheduler: MockRelayScheduler let clock: MutableClock @@ -1644,7 +1642,6 @@ private final class MockRelaySessionFactory: NostrRelaySessionProtocol { } private final class MockRelayConnection: NostrRelayConnectionProtocol { - private let url: String private let pingError: Error? private let sendError: Error? private var receiveHandler: ((Result) -> Void)? @@ -1662,8 +1659,7 @@ private final class MockRelayConnection: NostrRelayConnectionProtocol { } } - init(url: String, pingError: Error? = nil, sendError: Error? = nil) { - self.url = url + init(url _: String, pingError: Error? = nil, sendError: Error? = nil) { self.pingError = pingError self.sendError = sendError } diff --git a/bitchatTests/Services/UnifiedPeerServiceTests.swift b/bitchatTests/Services/UnifiedPeerServiceTests.swift index 172bf5b1..17b30def 100644 --- a/bitchatTests/Services/UnifiedPeerServiceTests.swift +++ b/bitchatTests/Services/UnifiedPeerServiceTests.swift @@ -214,18 +214,6 @@ private final class TestIdentityManager: SecureIdentityStateManagerProtocol { socialIdentities[identity.fingerprint] = identity } - func getFavorites() -> Set { - favorites - } - - func setFavorite(_ fingerprint: String, isFavorite: Bool) { - if isFavorite { - favorites.insert(fingerprint) - } else { - favorites.remove(fingerprint) - } - } - func isFavorite(fingerprint: String) -> Bool { favorites.contains(fingerprint) } @@ -266,8 +254,6 @@ private final class TestIdentityManager: SecureIdentityStateManagerProtocol { func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) {} - func updateHandshakeState(peerID: PeerID, state: HandshakeState) {} - func clearAllIdentityData() { socialIdentities.removeAll() favorites.removeAll() @@ -308,10 +294,6 @@ private final class TestIdentityManager: SecureIdentityStateManagerProtocol { false } - func effectiveTrustLevel(for fingerprint: String) -> TrustLevel { - verified.contains(fingerprint) ? .verified : .unknown - } - func lastVouchBatchSent(to fingerprint: String) -> Date? { nil } diff --git a/bitchatTests/TestUtilities/TestConstants.swift b/bitchatTests/TestUtilities/TestConstants.swift index b64e6378..83f5b46b 100644 --- a/bitchatTests/TestUtilities/TestConstants.swift +++ b/bitchatTests/TestUtilities/TestConstants.swift @@ -25,9 +25,5 @@ struct TestConstants { static let testNickname4 = "David" static let testMessage1 = "Hello, World!" - static let testMessage2 = "How are you?" - static let testMessage3 = "This is a test message" static let testLongMessage = String(repeating: "This is a long message. ", count: 100) - - static let testSignature = Data(repeating: 0xAB, count: 64) } diff --git a/bitchatTests/TestUtilities/TestHelpers.swift b/bitchatTests/TestUtilities/TestHelpers.swift index d92f0474..45b432cb 100644 --- a/bitchatTests/TestUtilities/TestHelpers.swift +++ b/bitchatTests/TestUtilities/TestHelpers.swift @@ -12,20 +12,7 @@ import BitFoundation @testable import bitchat final class TestHelpers { - - // MARK: - Key Generation - - static func generateTestKeyPair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) { - let privateKey = Curve25519.KeyAgreement.PrivateKey() - let publicKey = privateKey.publicKey - return (privateKey, publicKey) - } - - static func generateTestIdentity(peerID: String, nickname: String) -> (peerID: String, nickname: String, privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) { - let (privateKey, publicKey) = generateTestKeyPair() - return (peerID: peerID, nickname: nickname, privateKey: privateKey, publicKey: publicKey) - } - + // MARK: - Message Creation static func createTestMessage( @@ -78,11 +65,7 @@ final class TestHelpers { } return data } - - static func generateTestPeerID() -> String { - return "PEER" + UUID().uuidString.prefix(8) - } - + // MARK: - Async Helpers static func waitFor(_ condition: @escaping () -> Bool, timeout: TimeInterval = TestConstants.defaultTimeout) async throws { @@ -110,32 +93,10 @@ final class TestHelpers { } return true } - - static func expectAsync( - timeout: TimeInterval = TestConstants.defaultTimeout, - operation: @escaping () async throws -> T - ) async throws -> T { - return try await withThrowingTaskGroup(of: T.self) { group in - group.addTask { - return try await operation() - } - - group.addTask { - try await sleep(1) - throw TestError.timeout - } - - let result = try await group.next()! - group.cancelAll() - return result - } - } } enum TestError: Error { case timeout - case unexpectedValue - case testFailure(String) } // MARK: - Private chat seeding (ConversationStore migration) @@ -164,18 +125,6 @@ extension ChatViewModel { } } - /// Test-only replacement for `messages.removeAll()`: empties a public - /// channel's conversation. - @MainActor - func clearPublicMessages(for channel: ChannelID = .mesh) { - conversations.clear(ConversationID(channelID: channel)) - } - - /// Test-only: drops every private chat and unread flag. - @MainActor - func clearAllPrivateChats() { - conversations.removeAllDirectConversations() - } } func sleep(_ seconds: TimeInterval) async throws { diff --git a/localPackages/Arti/Sources/TorManager.swift b/localPackages/Arti/Sources/TorManager.swift index 2e252fad..384f5239 100644 --- a/localPackages/Arti/Sources/TorManager.swift +++ b/localPackages/Arti/Sources/TorManager.swift @@ -30,12 +30,6 @@ private func arti_bootstrap_progress() -> Int32 @_silgen_name("arti_bootstrap_summary") private func arti_bootstrap_summary(_ buf: UnsafeMutablePointer, _ len: Int32) -> Int32 -@_silgen_name("arti_go_dormant") -private func arti_go_dormant() -> Int32 - -@_silgen_name("arti_wake") -private func arti_wake() -> Int32 - /// Arti-based Tor integration for BitChat. /// - Boots a local Arti client and exposes a SOCKS5 proxy /// on 127.0.0.1:socksPort. All app networking should await readiness and @@ -83,7 +77,6 @@ public final class TorManager: ObservableObject { private var bootstrapMonitorStarted = false private var pathMonitor: NWPathMonitor? private var isAppForeground: Bool = true - private var isDormant: Bool = false private var lastRestartAt: Date? = nil private var startedAt: Date? = nil // Tracks initial startup time for grace period private(set) var allowAutoStart: Bool = false @@ -102,7 +95,6 @@ public final class TorManager: ObservableObject { } guard !didStart else { return } didStart = true - isDormant = false isStarting = true startedAt = Date() // Track startup time for grace period SecureLogger.debug("TorManager: startIfNeeded() - startedAt set", category: .session) @@ -353,7 +345,6 @@ public final class TorManager: ObservableObject { } await MainActor.run { - self.isDormant = false self.isReady = false self.socksReady = false self.bootstrapProgress = 0 @@ -383,7 +374,6 @@ public final class TorManager: ObservableObject { self.bootstrapProgress = 0 self.bootstrapSummary = "" self.isStarting = true - self.isDormant = false self.lastRestartAt = Date() } diff --git a/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift b/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift index 34ce30d8..e326eed2 100644 --- a/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift +++ b/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift @@ -105,10 +105,6 @@ public struct BinaryProtocol { // Field offsets within packet header public struct Offsets { - static let version = 0 - static let type = 1 - static let ttl = 2 - static let timestamp = 3 public static let flags = 11 // After version(1) + type(1) + ttl(1) + timestamp(8) } diff --git a/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift b/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift index ced3a9b7..80ebf506 100644 --- a/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift +++ b/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift @@ -341,21 +341,3 @@ extension BitchatMessage { } } -extension Array where Element == BitchatMessage { - /// Filters out empty ones and deduplicate by ID while preserving order (from oldest to newest) - public func cleanedAndDeduped() -> [Element] { - let arr = filter { $0.content.trimmed.isEmpty == false } - guard arr.count > 1 else { - return arr - } - var seen = Set() - var dedup: [BitchatMessage] = [] - for m in arr.sorted(by: { $0.timestamp < $1.timestamp }) { - if !seen.contains(m.id) { - dedup.append(m) - seen.insert(m.id) - } - } - return dedup - } -} diff --git a/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift b/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift index ca29a2b2..db286ae3 100644 --- a/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift +++ b/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift @@ -7,7 +7,6 @@ // import struct Foundation.Data -import struct Foundation.Date /// The core packet structure for all BitChat protocol messages. /// Encapsulates all data needed for routing through the mesh network, @@ -37,35 +36,7 @@ public struct BitchatPacket: Codable { self.route = route self.isRSR = isRSR } - - // Convenience initializer for new binary format - init(type: UInt8, ttl: UInt8, senderID: PeerID, payload: Data, isRSR: Bool = false) { - self.version = 1 - self.type = type - // Convert hex string peer ID to binary data (8 bytes) - var senderData = Data() - var tempID = senderID.id - while tempID.count >= 2 { - let hexByte = String(tempID.prefix(2)) - if let byte = UInt8(hexByte, radix: 16) { - senderData.append(byte) - } - tempID = String(tempID.dropFirst(2)) - } - self.senderID = senderData - self.recipientID = nil - self.timestamp = UInt64(Date().timeIntervalSince1970 * 1000) // milliseconds - self.payload = payload - self.signature = nil - self.ttl = ttl - self.route = nil - self.isRSR = isRSR - } - - var data: Data? { - BinaryProtocol.encode(self) - } - + public func toBinaryData(padding: Bool = true) -> Data? { BinaryProtocol.encode(self, padding: padding) } diff --git a/localPackages/BitLogger/Sources/OSLog+Categories.swift b/localPackages/BitLogger/Sources/OSLog+Categories.swift index 1d74e27e..4810b2fb 100644 --- a/localPackages/BitLogger/Sources/OSLog+Categories.swift +++ b/localPackages/BitLogger/Sources/OSLog+Categories.swift @@ -18,6 +18,5 @@ public extension OSLog { static let keychain = OSLog(subsystem: subsystem, category: "keychain") static let session = OSLog(subsystem: subsystem, category: "session") static let security = OSLog(subsystem: subsystem, category: "security") - static let handshake = OSLog(subsystem: subsystem, category: "handshake") static let sync = OSLog(subsystem: subsystem, category: "sync") } diff --git a/localPackages/BitLogger/Sources/SecureLogger.swift b/localPackages/BitLogger/Sources/SecureLogger.swift index 5aaffd53..e12fda0f 100644 --- a/localPackages/BitLogger/Sources/SecureLogger.swift +++ b/localPackages/BitLogger/Sources/SecureLogger.swift @@ -202,10 +202,6 @@ public extension SecureLogger { } } - static func debug(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { - logSecurityEvent(event, level: .debug, file: file, line: line, function: function) - } - static func info(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { logSecurityEvent(event, level: .info, file: file, line: line, function: function) } @@ -280,14 +276,3 @@ private extension SecureLogger { } } -// MARK: - Migration Helper - -/// Helper to migrate from print statements to SecureLogger -/// Usage: Replace print(...) with secureLog(...) -public func secureLog(_ items: Any..., separator: String = " ", terminator: String = "\n", - file: String = #file, line: Int = #line, function: String = #function) { - #if DEBUG - let message = items.map { String(describing: $0) }.joined(separator: separator) - SecureLogger.debug(message, file: file, line: line, function: function) - #endif -}