diff --git a/README.md b/README.md index 4cd7b2b787..28b40d0c1b 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md) + [Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) [Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) @@ -377,8 +379,9 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A - ✅ Using mobile profiles from the desktop app. - ✅ Private notes. - ✅ Improve sending videos (including encryption of locally stored videos). +- ✅ Post-quantum resistant key exchange in double ratchet protocol. +- 🏗 Improve stability and reduce battery usage. - 🏗 Improve experience for the new users. -- 🏗 Post-quantum resistant key exchange in double ratchet protocol. - 🏗 Large groups, communities and public channels. - 🏗 Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic). - Privacy & security slider - a simple way to set all settings at once. diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 365f23c33e..a099069f77 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -21,6 +21,8 @@ public let CURRENT_CHAT_VERSION: Int = 2 // version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core) public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION) +private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock") + enum TerminalItem: Identifiable { case cmd(Date, ChatCommand) case resp(Date, ChatResponse) @@ -1566,34 +1568,30 @@ func processReceivedMsg(_ res: ChatResponse) async { m.removeChat(mergedContact.id) } } - case let .contactsSubscribed(_, contactRefs): - await updateContactsStatus(contactRefs, status: .connected) - case let .contactsDisconnected(_, contactRefs): - await updateContactsStatus(contactRefs, status: .disconnected) - case let .contactSubSummary(_, contactSubscriptions): - await MainActor.run { - for sub in contactSubscriptions { -// no need to update contact here, and it is slow -// if active(user) { -// m.updateContact(sub.contact) -// } - if let err = sub.contactError { - processContactSubError(sub.contact, err) - } else { - m.setContactNetworkStatus(sub.contact, .connected) - } - } - } case let .networkStatus(status, connections): - await MainActor.run { + // dispatch queue to synchronize access + networkStatusesLock.sync { + var ns = m.networkStatuses + // slow loop is on the background thread for cId in connections { - m.networkStatuses[cId] = status + ns[cId] = status + } + // fast model update is on the main thread + DispatchQueue.main.sync { + m.networkStatuses = ns } } case let .networkStatuses(_, statuses): () - await MainActor.run { + // dispatch queue to synchronize access + networkStatusesLock.sync { + var ns = m.networkStatuses + // slow loop is on the background thread for s in statuses { - m.networkStatuses[s.agentConnId] = s.networkStatus + ns[s.agentConnId] = s.networkStatus + } + // fast model update is on the main thread + DispatchQueue.main.sync { + m.networkStatuses = ns } } case let .newChatItem(user, aChatItem): @@ -1944,26 +1942,6 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async { } } -func updateContactsStatus(_ contactRefs: [ContactRef], status: NetworkStatus) async { - let m = ChatModel.shared - await MainActor.run { - for c in contactRefs { - m.networkStatuses[c.agentConnId] = status - } - } -} - -func processContactSubError(_ contact: Contact, _ chatError: ChatError) { - let m = ChatModel.shared - var err: String - switch chatError { - case .errorAgent(agentError: .BROKER(_, .NETWORK)): err = "network" - case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted" - default: err = String(describing: chatError) - } - m.setContactNetworkStatus(contact, .error(connectionError: err)) -} - func refreshCallInvitations() throws { let m = ChatModel.shared let callInvitations = try justRefreshCallInvitations() diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 36d3e318ed..33bd46e393 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -17,6 +17,7 @@ struct ChatView: View { @Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss @Environment(\.presentationMode) var presentationMode + @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @@ -234,7 +235,9 @@ struct ChatView: View { private func initChatView() { let cInfo = chat.chatInfo - if case let .direct(contact) = cInfo { + // This check prevents the call to apiContactInfo after the app is suspended, and the database is closed. + if case .active = scenePhase, + case let .direct(contact) = cInfo { Task { do { let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId) @@ -979,8 +982,9 @@ struct ChatView: View { title: NSLocalizedString("Hide", comment: "chat item action"), image: UIImage(systemName: "eye.slash") ) { _ in - // With animation it looks bad because of UIKit context menu involved - revealed = false + withAnimation { + revealed = false + } } } @@ -1049,8 +1053,9 @@ struct ChatView: View { title: NSLocalizedString("Reveal", comment: "chat item action"), image: UIImage(systemName: "eye") ) { _ in - // With animation it looks bad because of UIKit context menu involved - revealed = true + withAnimation { + revealed = true + } } } diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index 741af6f08f..922fc84e53 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -12,6 +12,7 @@ private let fillColorLight = Color(uiColor: UIColor(red: 0.99, green: 0.99, blue struct UserPicker: View { @EnvironmentObject var m: ChatModel @Environment(\.colorScheme) var colorScheme + @Environment(\.scenePhase) var scenePhase @Binding var showSettings: Bool @Binding var showConnectDesktop: Bool @Binding var userPickerVisible: Bool @@ -91,7 +92,10 @@ struct UserPicker: View { .opacity(userPickerVisible ? 1.0 : 0.0) .onAppear { do { - m.users = try listUsers() + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { + m.users = try listUsers() + } } catch let error { logger.error("Error loading users \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 3f835e25d4..0ee6baa765 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -166,8 +166,10 @@ private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) ) let m = ChatModel.shared do { + AppChatState.shared.set(.active) m.currentUser = try apiCreateActiveUser(profile) - if m.users.isEmpty { + // .isEmpty check is redundant here, but it makes it clearer what is going on + if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { try startChat() withAnimation { onboardingStageDefault.set(.step3_CreateSimpleXAddress) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index f04880ee91..0f8bff18ea 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -631,6 +631,7 @@ Admins can block a member for all. + Администраторите могат да блокират член за всички. No comment provided by engineer. @@ -690,6 +691,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Всички ваши контакти, разговори и файлове ще бъдат сигурно криптирани и качени на парчета в конфигурираните XFTP релета. No comment provided by engineer. @@ -819,6 +821,7 @@ App data migration + Миграция на данните от приложението No comment provided by engineer. @@ -858,14 +861,17 @@ Apply + Приложи No comment provided by engineer. Archive and upload + Архивиране и качване No comment provided by engineer. Archiving database + Архивиране на база данни No comment provided by engineer. @@ -1060,6 +1066,7 @@ Cancel migration + Отмени миграцията No comment provided by engineer. @@ -1165,6 +1172,7 @@ Chat migrated! + Чатът е мигриран! No comment provided by engineer. @@ -1263,6 +1271,7 @@ Confirm network settings + Потвърди мрежовите настройки No comment provided by engineer. @@ -1277,10 +1286,12 @@ Confirm that you remember database passphrase to migrate it. + Потвърдете, че помните паролата на базата данни, преди да я мигрирате. No comment provided by engineer. Confirm upload + Потвърди качването No comment provided by engineer. @@ -1544,6 +1555,7 @@ This is your own one-time link! Creating archive link + Създаване на архивен линк No comment provided by engineer. @@ -1768,6 +1780,7 @@ This cannot be undone! Delete database from this device + Изтриване на базата данни от това устройство No comment provided by engineer. @@ -2062,6 +2075,7 @@ This cannot be undone! Download failed + Неуспешно изтегляне No comment provided by engineer. @@ -2071,10 +2085,12 @@ This cannot be undone! Downloading archive + Архива се изтегля No comment provided by engineer. Downloading link details + Подробности за линка се изтеглят No comment provided by engineer. @@ -2134,6 +2150,7 @@ This cannot be undone! Enable in direct chats (BETA)! + Активиране в личните чатове (БЕТА)! No comment provided by engineer. @@ -2253,6 +2270,7 @@ This cannot be undone! Enter passphrase + Въведи парола No comment provided by engineer. @@ -2317,6 +2335,7 @@ This cannot be undone! Error allowing contact PQ encryption + Грешка при разрешаване на PQ криптиране за контакт No comment provided by engineer. @@ -2411,6 +2430,7 @@ This cannot be undone! Error downloading the archive + Грешка при изтеглянето на архива No comment provided by engineer. @@ -2490,6 +2510,7 @@ This cannot be undone! Error saving settings + Грешка при запазване на настройките when migrating @@ -2564,10 +2585,12 @@ This cannot be undone! Error uploading the archive + Грешка при качването на архива No comment provided by engineer. Error verifying passphrase: + Грешка при проверката на паролата: No comment provided by engineer. @@ -2622,6 +2645,7 @@ This cannot be undone! Exported file doesn't exist + Експортираният файл не съществува No comment provided by engineer. @@ -2696,10 +2720,12 @@ This cannot be undone! Finalize migration + Завърши миграцията No comment provided by engineer. Finalize migration on another device. + Завършете миграцията на другото устройство. No comment provided by engineer. @@ -2994,6 +3020,7 @@ This cannot be undone! Hungarian interface + Унгарски интерфейс No comment provided by engineer. @@ -3063,10 +3090,12 @@ This cannot be undone! Import failed + Неуспешно импортиране No comment provided by engineer. Importing archive + Импортиране на архив No comment provided by engineer. @@ -3086,6 +3115,7 @@ This cannot be undone! In order to continue, chat should be stopped. + За да продължите, чатът трябва да бъде спрян. No comment provided by engineer. @@ -3202,6 +3232,7 @@ This cannot be undone! Invalid migration confirmation + Невалидно потвърждение за мигриране No comment provided by engineer. @@ -3574,6 +3605,7 @@ This is your link for group %@! Message too large + Съобщението е твърде голямо No comment provided by engineer. @@ -3601,26 +3633,32 @@ This is your link for group %@! Migrate device + Мигрирай устройството No comment provided by engineer. Migrate from another device + Мигриране от друго устройство No comment provided by engineer. Migrate here + Мигрирай тук No comment provided by engineer. Migrate to another device + Миграция към друго устройство No comment provided by engineer. Migrate to another device via QR code. + Мигрирайте към друго устройство чрез QR код. No comment provided by engineer. Migrating + Мигриране No comment provided by engineer. @@ -3630,6 +3668,7 @@ This is your link for group %@! Migration complete + Миграцията е завършена No comment provided by engineer. @@ -3993,6 +4032,7 @@ This is your link for group %@! Open migration to another device + Отвори миграцията към друго устройство authentication reason @@ -4012,6 +4052,7 @@ This is your link for group %@! Or paste archive link + Или постави архивен линк No comment provided by engineer. @@ -4021,6 +4062,7 @@ This is your link for group %@! Or securely share this file link + Или сигурно споделете този линк към файла No comment provided by engineer. @@ -4110,6 +4152,7 @@ This is your link for group %@! Picture-in-picture calls + Обаждания "картина в картина" No comment provided by engineer. @@ -4134,6 +4177,7 @@ This is your link for group %@! Please confirm that network settings are correct for this device. + Моля, потвърдете, че мрежовите настройки са правилни за това устройство. No comment provided by engineer. @@ -4195,6 +4239,7 @@ Error: %@ Post-quantum E2EE + Постквантово E2EE No comment provided by engineer. @@ -4334,10 +4379,12 @@ Error: %@ Push server + Push сървър No comment provided by engineer. Quantum resistant encryption + Квантово устойчиво криптиране No comment provided by engineer. @@ -4527,10 +4574,12 @@ Error: %@ Repeat download + Повтори изтеглянето No comment provided by engineer. Repeat import + Повтори импортирането No comment provided by engineer. @@ -4540,6 +4589,7 @@ Error: %@ Repeat upload + Повтори качването No comment provided by engineer. @@ -4644,6 +4694,7 @@ Error: %@ Safer groups + По-безопасни групи No comment provided by engineer. @@ -5013,6 +5064,7 @@ Error: %@ Set passphrase + Задаване на парола No comment provided by engineer. @@ -5072,6 +5124,7 @@ Error: %@ Show QR code + Покажи QR код No comment provided by engineer. @@ -5216,6 +5269,7 @@ Error: %@ Stop chat + Спри чата No comment provided by engineer. @@ -5260,6 +5314,7 @@ Error: %@ Stopping chat + Спиране на чата No comment provided by engineer. @@ -5511,10 +5566,12 @@ It can happen because of some bug or when the connection is compromised. This chat is protected by end-to-end encryption. + Този чат е защитен чрез криптиране от край до край. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Този чат е защитен от квантово устойчиво криптиране от край до край. E2EE info chat item @@ -5813,6 +5870,7 @@ To connect, please ask your contact to create another connection link and check Upload failed + Неуспешно качване No comment provided by engineer. @@ -5822,6 +5880,7 @@ To connect, please ask your contact to create another connection link and check Uploading archive + Архивът се качва No comment provided by engineer. @@ -5876,6 +5935,7 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. + Използвайте приложението по време на разговора. No comment provided by engineer. @@ -5915,10 +5975,12 @@ To connect, please ask your contact to create another connection link and check Verify database passphrase + Проверете паролата на базата данни No comment provided by engineer. Verify passphrase + Провери паролата No comment provided by engineer. @@ -6013,6 +6075,7 @@ To connect, please ask your contact to create another connection link and check Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Внимание: стартирането на чата на множество устройства не се поддържа и ще доведе до неуспешно изпращане на съобщения No comment provided by engineer. @@ -6037,6 +6100,7 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long + Съобщението при посрещане е твърде дълго No comment provided by engineer. @@ -6187,6 +6251,7 @@ Repeat join request? You can give another try. + Можете да опитате още веднъж. No comment provided by engineer. @@ -7094,6 +7159,7 @@ SimpleX сървърите не могат да видят вашия профи quantum resistant e2e encryption + квантово устойчиво e2e криптиране chat item text @@ -7173,6 +7239,7 @@ SimpleX сървърите не могат да видят вашия профи standard end-to-end encryption + стандартно криптиране от край до край chat item text diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 6788b5e8e6..7ad19ff86f 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ heruntergeladen No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ hochgeladen No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Warnung**: Das Archiv wird gelöscht. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Administratoren können ein Gruppenmitglied für Alle blockieren. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + App-Daten-Migration No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Anwenden No comment provided by engineer. Archive and upload + Archivieren und Hochladen No comment provided by engineer. Archiving database + Datenbank wird archiviert No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Migration abbrechen No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Chat wurde migriert! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Wählen Sie auf dem neuen Gerät _Von einem anderen Gerät migrieren_ und scannen Sie den QR-Code. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Bestätigen Sie die Netzwerkeinstellungen No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern. No comment provided by engineer. Confirm upload + Hochladen bestätigen No comment provided by engineer. @@ -1544,6 +1560,7 @@ Das ist Ihr eigener Einmal-Link! Creating archive link + Archiv-Link erzeugen No comment provided by engineer. @@ -1768,6 +1785,7 @@ Das kann nicht rückgängig gemacht werden! Delete database from this device + Datenbank auf diesem Gerät löschen No comment provided by engineer. @@ -2062,6 +2080,7 @@ Das kann nicht rückgängig gemacht werden! Download failed + Herunterladen fehlgeschlagen No comment provided by engineer. @@ -2071,10 +2090,12 @@ Das kann nicht rückgängig gemacht werden! Downloading archive + Archiv wird heruntergeladen No comment provided by engineer. Downloading link details + Link-Details werden heruntergeladen No comment provided by engineer. @@ -2134,6 +2155,7 @@ Das kann nicht rückgängig gemacht werden! Enable in direct chats (BETA)! + Kann in direkten Chats aktiviert werden (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ Das kann nicht rückgängig gemacht werden! Enter passphrase + Passwort eingeben No comment provided by engineer. @@ -2317,6 +2340,7 @@ Das kann nicht rückgängig gemacht werden! Error allowing contact PQ encryption + Fehler beim Zulassen der Kontakt-PQ-Verschlüsselung No comment provided by engineer. @@ -2411,6 +2435,7 @@ Das kann nicht rückgängig gemacht werden! Error downloading the archive + Fehler beim Herunterladen des Archivs No comment provided by engineer. @@ -2490,6 +2515,7 @@ Das kann nicht rückgängig gemacht werden! Error saving settings + Fehler beim Abspeichern der Einstellungen when migrating @@ -2564,10 +2590,12 @@ Das kann nicht rückgängig gemacht werden! Error uploading the archive + Fehler beim Hochladen des Archivs No comment provided by engineer. Error verifying passphrase: + Fehler bei der Überprüfung des Passworts: No comment provided by engineer. @@ -2622,6 +2650,7 @@ Das kann nicht rückgängig gemacht werden! Exported file doesn't exist + Die exportierte Datei ist nicht vorhanden No comment provided by engineer. @@ -2696,10 +2725,12 @@ Das kann nicht rückgängig gemacht werden! Finalize migration + Die Migration wird abgeschlossen No comment provided by engineer. Finalize migration on another device. + Die Migration auf dem anderen Gerät wird abgeschlossen. No comment provided by engineer. @@ -2994,6 +3025,7 @@ Das kann nicht rückgängig gemacht werden! Hungarian interface + Ungarische Bedienoberfläche No comment provided by engineer. @@ -3063,10 +3095,12 @@ Das kann nicht rückgängig gemacht werden! Import failed + Import ist fehlgeschlagen No comment provided by engineer. Importing archive + Archiv wird importiert No comment provided by engineer. @@ -3086,6 +3120,7 @@ Das kann nicht rückgängig gemacht werden! In order to continue, chat should be stopped. + Um fortzufahren, sollte der Chat beendet werden. No comment provided by engineer. @@ -3202,6 +3237,7 @@ Das kann nicht rückgängig gemacht werden! Invalid migration confirmation + Migrations-Bestätigung ungültig No comment provided by engineer. @@ -3574,6 +3610,7 @@ Das ist Ihr Link für die Gruppe %@! Message too large + Die Nachricht ist zu lang No comment provided by engineer. @@ -3593,34 +3630,42 @@ Das ist Ihr Link für die Gruppe %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. No comment provided by engineer. Migrate device + Gerät migrieren No comment provided by engineer. Migrate from another device + Von einem anderen Gerät migrieren No comment provided by engineer. Migrate here + Hierher migrieren No comment provided by engineer. Migrate to another device + Auf ein anderes Gerät migrieren No comment provided by engineer. Migrate to another device via QR code. + Daten können über einen QR-Code auf ein anderes Gerät migriert werden. No comment provided by engineer. Migrating + Migrieren No comment provided by engineer. @@ -3630,6 +3675,7 @@ Das ist Ihr Link für die Gruppe %@! Migration complete + Migration abgeschlossen No comment provided by engineer. @@ -3993,6 +4039,7 @@ Das ist Ihr Link für die Gruppe %@! Open migration to another device + Migration auf ein anderes Gerät öffnen authentication reason @@ -4012,6 +4059,7 @@ Das ist Ihr Link für die Gruppe %@! Or paste archive link + Oder fügen Sie den Archiv-Link ein No comment provided by engineer. @@ -4021,6 +4069,7 @@ Das ist Ihr Link für die Gruppe %@! Or securely share this file link + Oder teilen Sie diesen Datei-Link sicher No comment provided by engineer. @@ -4110,6 +4159,7 @@ Das ist Ihr Link für die Gruppe %@! Picture-in-picture calls + Bild-in-Bild-Anrufe No comment provided by engineer. @@ -4134,6 +4184,7 @@ Das ist Ihr Link für die Gruppe %@! Please confirm that network settings are correct for this device. + Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Fehler: %@ Post-quantum E2EE + Post-Quantum E2E-Verschlüsselung No comment provided by engineer. @@ -4334,10 +4386,12 @@ Fehler: %@ Push server + Push-Server No comment provided by engineer. Quantum resistant encryption + Quantum-resistente Verschlüsselung No comment provided by engineer. @@ -4527,10 +4581,12 @@ Fehler: %@ Repeat download + Herunterladen wiederholen No comment provided by engineer. Repeat import + Import wiederholen No comment provided by engineer. @@ -4540,6 +4596,7 @@ Fehler: %@ Repeat upload + Hochladen wiederholen No comment provided by engineer. @@ -4644,6 +4701,7 @@ Fehler: %@ Safer groups + Sicherere Gruppen No comment provided by engineer. @@ -5013,6 +5071,7 @@ Fehler: %@ Set passphrase + Passwort festlegen No comment provided by engineer. @@ -5072,6 +5131,7 @@ Fehler: %@ Show QR code + QR-Code anzeigen No comment provided by engineer. @@ -5216,6 +5276,7 @@ Fehler: %@ Stop chat + Chat beenden No comment provided by engineer. @@ -5260,6 +5321,7 @@ Fehler: %@ Stopping chat + Chat wird beendet No comment provided by engineer. @@ -5511,10 +5573,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This chat is protected by end-to-end encryption. + Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt. E2EE info chat item @@ -5554,7 +5618,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro To ask any questions and to receive updates: - Um Fragen zu stellen und Aktualisierungen zu erhalten: + Um Fragen zu stellen und aktuelle Informationen zu erhalten: No comment provided by engineer. @@ -5813,6 +5877,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Upload failed + Hochladen fehlgeschlagen No comment provided by engineer. @@ -5822,6 +5887,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Uploading archive + Archiv wird hochgeladen No comment provided by engineer. @@ -5876,6 +5942,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use the app while in the call. + Die App kann während eines Anrufs genutzt werden. No comment provided by engineer. @@ -5915,10 +5982,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verify database passphrase + Überprüfen Sie das Datenbank-Passwort No comment provided by engineer. Verify passphrase + Überprüfen Sie das Passwort No comment provided by engineer. @@ -6013,6 +6082,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen No comment provided by engineer. @@ -6037,6 +6107,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Welcome message is too long + Die Begrüßungsmeldung ist zu lang No comment provided by engineer. @@ -6096,6 +6167,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s You **must not** use the same database on two devices. + Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Verbindungsanfrage wiederholen? You can give another try. + Sie können es nochmal probieren. No comment provided by engineer. @@ -6435,7 +6508,7 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später Your contacts will remain connected. - Ihre Kontakte bleiben verbunden. + Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. @@ -7094,6 +7167,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. quantum resistant e2e encryption + Quantum-resistente E2E-Verschlüsselung chat item text @@ -7173,6 +7247,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. standard end-to-end encryption + Standard-Ende-zu-Ende-Verschlüsselung chat item text diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 9cc9583e2e..99a117d786 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ downloaded No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ cargado No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Tenga en cuenta**: usar la misma base de datos en dos dispositivos interrumpirá el descifrado de mensajes de sus conexiones, como protección de seguridad. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Atención**: el archivo será eliminado. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Los admins pueden bloquear un miembro por todos. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Todos sus contactos, conversaciones y archivos se cifrarán de forma segura y se subirán en fragmentos hacia puntos XFTP configurados. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Migración de datos de la aplicación No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Aplicar No comment provided by engineer. Archive and upload + Archivar y transferir No comment provided by engineer. Archiving database + Archivando base de datos No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Cancelar migración No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Chat transferido ! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Elija _Migrar desde otro dispositivo_ en el nuevo dispositivo y escanee el código QR. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Confirmar los ajustes de red No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Confirme que recuerda la frase secreta de la base de datos para migrarla. No comment provided by engineer. Confirm upload + Confirmar subida No comment provided by engineer. @@ -1544,6 +1560,7 @@ This is your own one-time link! Creating archive link + Creando enlace de archivo No comment provided by engineer. @@ -1768,6 +1785,7 @@ This cannot be undone! Delete database from this device + Eliminar base de datos de este dispositivo No comment provided by engineer. @@ -2062,6 +2080,7 @@ This cannot be undone! Download failed + Error en la descarga No comment provided by engineer. @@ -2071,10 +2090,12 @@ This cannot be undone! Downloading archive + Descargando archivo No comment provided by engineer. Downloading link details + Descargando detalles del enlace No comment provided by engineer. @@ -2134,6 +2155,7 @@ This cannot be undone! Enable in direct chats (BETA)! + Activar en chats directos (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ This cannot be undone! Enter passphrase + Introducir frase de contraseña No comment provided by engineer. @@ -2317,6 +2340,7 @@ This cannot be undone! Error allowing contact PQ encryption + Error al permitir cifrado PQ al contacto No comment provided by engineer. @@ -2411,6 +2435,7 @@ This cannot be undone! Error downloading the archive + Error al descargar el archivo No comment provided by engineer. @@ -2490,6 +2515,7 @@ This cannot be undone! Error saving settings + Error al guardar ajustes when migrating @@ -2564,10 +2590,12 @@ This cannot be undone! Error uploading the archive + Error al subir el archivo No comment provided by engineer. Error verifying passphrase: + Error al verificar la frase de contraseña: No comment provided by engineer. @@ -2622,6 +2650,7 @@ This cannot be undone! Exported file doesn't exist + El archivo exportado no existe No comment provided by engineer. @@ -2696,10 +2725,12 @@ This cannot be undone! Finalize migration + Finalizar la migración No comment provided by engineer. Finalize migration on another device. + Finalizar la migración en otro dispositivo. No comment provided by engineer. @@ -2994,6 +3025,7 @@ This cannot be undone! Hungarian interface + Interfaz húngara No comment provided by engineer. @@ -3063,10 +3095,12 @@ This cannot be undone! Import failed + Error de importación No comment provided by engineer. Importing archive + Importando archivo No comment provided by engineer. @@ -3086,6 +3120,7 @@ This cannot be undone! In order to continue, chat should be stopped. + Para continuar, el chat debe ser interrumpido. No comment provided by engineer. @@ -3202,6 +3237,7 @@ This cannot be undone! Invalid migration confirmation + Confirmación de migración inválida No comment provided by engineer. @@ -3574,6 +3610,7 @@ This is your link for group %@! Message too large + Mensaje demasiado grande No comment provided by engineer. @@ -3593,34 +3630,42 @@ This is your link for group %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo** con perfecta confidencialidad, repudio y recuperación tras ataques. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo resistente a la computación cuántica** con perfecta confidencialidad, repudio y recuperación tras ataques. No comment provided by engineer. Migrate device + Migrar dispositivo No comment provided by engineer. Migrate from another device + Migrar desde otro dispositivo No comment provided by engineer. Migrate here + Migrar aquí No comment provided by engineer. Migrate to another device + Migrar hacia otro dispositivo No comment provided by engineer. Migrate to another device via QR code. + Migrar hacia otro dispositivo mediante código QR. No comment provided by engineer. Migrating + Migrando No comment provided by engineer. @@ -3630,6 +3675,7 @@ This is your link for group %@! Migration complete + Migración completada No comment provided by engineer. @@ -3993,6 +4039,7 @@ This is your link for group %@! Open migration to another device + Abrir la migración hacia otro dispositivo authentication reason @@ -4012,6 +4059,7 @@ This is your link for group %@! Or paste archive link + O pegar enlace del archivo No comment provided by engineer. @@ -4021,6 +4069,7 @@ This is your link for group %@! Or securely share this file link + O comparta de forma segura el enlace de este archivo No comment provided by engineer. @@ -4110,6 +4159,7 @@ This is your link for group %@! Picture-in-picture calls + Llamadas picture-in-picture No comment provided by engineer. @@ -4134,6 +4184,7 @@ This is your link for group %@! Please confirm that network settings are correct for this device. + Por favor confirme que la configuración de red es correcta para este dispositivo. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Error: %@ Post-quantum E2EE + E2EE postcuántica No comment provided by engineer. @@ -4334,10 +4386,12 @@ Error: %@ Push server + Servidor push No comment provided by engineer. Quantum resistant encryption + Cifrado resistente a la tecnología cuántica No comment provided by engineer. @@ -4527,10 +4581,12 @@ Error: %@ Repeat download + Repetir descarga No comment provided by engineer. Repeat import + Repetir importación No comment provided by engineer. @@ -4540,6 +4596,7 @@ Error: %@ Repeat upload + Repetir la carga No comment provided by engineer. @@ -4644,6 +4701,7 @@ Error: %@ Safer groups + Grupos más seguros No comment provided by engineer. @@ -5013,6 +5071,7 @@ Error: %@ Set passphrase + Definir frase de contraseña No comment provided by engineer. @@ -5072,6 +5131,7 @@ Error: %@ Show QR code + Mostrar código QR No comment provided by engineer. @@ -5216,6 +5276,7 @@ Error: %@ Stop chat + Detener el chat No comment provided by engineer. @@ -5260,6 +5321,7 @@ Error: %@ Stopping chat + Detención del chat No comment provided by engineer. @@ -5511,10 +5573,12 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This chat is protected by end-to-end encryption. + Este chat está protegido por cifrado de extremo a extremo. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Este chat está protegido por un cifrado de extremo a extremo resistente a tecnologías cuánticas. E2EE info chat item @@ -5814,6 +5878,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Upload failed + Error de carga No comment provided by engineer. @@ -5823,6 +5888,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Uploading archive + Subiendo el archivo No comment provided by engineer. @@ -5877,6 +5943,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Use the app while in the call. + Usar la app durante la llamada. No comment provided by engineer. @@ -5916,10 +5983,12 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Verify database passphrase + Verificar la contraseña de la base de datos No comment provided by engineer. Verify passphrase + Verificar frase de contraseña No comment provided by engineer. @@ -6014,6 +6083,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Advertencia: el inicio del chat en varios dispositivos no es compatible y provocará fallos en la entrega de mensajes No comment provided by engineer. @@ -6038,6 +6108,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Welcome message is too long + El mensaje de bienvenida es demasiado largo No comment provided by engineer. @@ -6097,6 +6168,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb You **must not** use the same database on two devices. + **No debe** usar la misma base de datos en dos dispositivos. No comment provided by engineer. @@ -6188,6 +6260,7 @@ Repeat join request? You can give another try. + Puede intentarlo de nuevo. No comment provided by engineer. @@ -7095,6 +7168,7 @@ Los servidores de SimpleX no pueden ver tu perfil. quantum resistant e2e encryption + cifrado e2e resistente a la cuántica chat item text @@ -7174,6 +7248,7 @@ Los servidores de SimpleX no pueden ver tu perfil. standard end-to-end encryption + cifrado estándar de extremo a extremo chat item text diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 70fb3145f9..1e335c00b0 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ téléchargé No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ envoyé No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Avertissement** : l'archive sera supprimée. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Les admins peuvent bloquer un membre pour tous. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Transfert des données de l'application No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Appliquer No comment provided by engineer. Archive and upload + Archiver et transférer No comment provided by engineer. Archiving database + Archivage de la base de données No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Annuler le transfert No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Messagerie transférée ! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Choisissez _Transferer depuis un autre appareil_ sur le nouvel appareil et scannez le code QR. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Confirmer les paramètres réseau No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer. No comment provided by engineer. Confirm upload + Confirmer la transmission No comment provided by engineer. @@ -1544,6 +1560,7 @@ Il s'agit de votre propre lien unique ! Creating archive link + Création d'un lien d'archive No comment provided by engineer. @@ -1768,6 +1785,7 @@ Cette opération ne peut être annulée ! Delete database from this device + Supprimer la base de données de cet appareil No comment provided by engineer. @@ -2062,6 +2080,7 @@ Cette opération ne peut être annulée ! Download failed + Échec du téléchargement No comment provided by engineer. @@ -2071,10 +2090,12 @@ Cette opération ne peut être annulée ! Downloading archive + Téléchargement de l'archive No comment provided by engineer. Downloading link details + Téléchargement des détails du lien No comment provided by engineer. @@ -2134,6 +2155,7 @@ Cette opération ne peut être annulée ! Enable in direct chats (BETA)! + Activer dans les conversations directes (BETA) ! No comment provided by engineer. @@ -2253,6 +2275,7 @@ Cette opération ne peut être annulée ! Enter passphrase + Entrer la phrase secrète No comment provided by engineer. @@ -2317,6 +2340,7 @@ Cette opération ne peut être annulée ! Error allowing contact PQ encryption + Erreur lors de la négociation du chiffrement PQ No comment provided by engineer. @@ -2411,6 +2435,7 @@ Cette opération ne peut être annulée ! Error downloading the archive + Erreur lors du téléchargement de l'archive No comment provided by engineer. @@ -2490,6 +2515,7 @@ Cette opération ne peut être annulée ! Error saving settings + Erreur lors de l'enregistrement des paramètres when migrating @@ -2564,10 +2590,12 @@ Cette opération ne peut être annulée ! Error uploading the archive + Erreur lors de l'envoi de l'archive No comment provided by engineer. Error verifying passphrase: + Erreur lors de la vérification de la phrase secrète : No comment provided by engineer. @@ -2622,6 +2650,7 @@ Cette opération ne peut être annulée ! Exported file doesn't exist + Le fichier exporté n'existe pas No comment provided by engineer. @@ -2696,10 +2725,12 @@ Cette opération ne peut être annulée ! Finalize migration + Finaliser le transfert No comment provided by engineer. Finalize migration on another device. + Finalisez le transfert sur l'autre appareil. No comment provided by engineer. @@ -2994,6 +3025,7 @@ Cette opération ne peut être annulée ! Hungarian interface + Interface en hongrois No comment provided by engineer. @@ -3063,10 +3095,12 @@ Cette opération ne peut être annulée ! Import failed + Échec de l'importation No comment provided by engineer. Importing archive + Importation de l'archive No comment provided by engineer. @@ -3086,6 +3120,7 @@ Cette opération ne peut être annulée ! In order to continue, chat should be stopped. + Pour continuer, le chat doit être interrompu. No comment provided by engineer. @@ -3202,6 +3237,7 @@ Cette opération ne peut être annulée ! Invalid migration confirmation + Confirmation de migration invalide No comment provided by engineer. @@ -3574,6 +3610,7 @@ Voici votre lien pour le groupe %@ ! Message too large + Message trop volumineux No comment provided by engineer. @@ -3593,34 +3630,42 @@ Voici votre lien pour le groupe %@ ! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. No comment provided by engineer. Migrate device + Transférer l'appareil No comment provided by engineer. Migrate from another device + Transférer depuis un autre appareil No comment provided by engineer. Migrate here + Transférer ici No comment provided by engineer. Migrate to another device + Transférer vers un autre appareil No comment provided by engineer. Migrate to another device via QR code. + Transférer vers un autre appareil via un code QR. No comment provided by engineer. Migrating + Transfert No comment provided by engineer. @@ -3630,6 +3675,7 @@ Voici votre lien pour le groupe %@ ! Migration complete + Transfert terminé No comment provided by engineer. @@ -3993,6 +4039,7 @@ Voici votre lien pour le groupe %@ ! Open migration to another device + Ouvrir le transfert vers un autre appareil authentication reason @@ -4012,6 +4059,7 @@ Voici votre lien pour le groupe %@ ! Or paste archive link + Ou coller le lien de l'archive No comment provided by engineer. @@ -4021,6 +4069,7 @@ Voici votre lien pour le groupe %@ ! Or securely share this file link + Ou partagez en toute sécurité le lien de ce fichier No comment provided by engineer. @@ -4110,6 +4159,7 @@ Voici votre lien pour le groupe %@ ! Picture-in-picture calls + Appels picture-in-picture No comment provided by engineer. @@ -4134,6 +4184,7 @@ Voici votre lien pour le groupe %@ ! Please confirm that network settings are correct for this device. + Veuillez confirmer que les paramètres réseau de cet appareil sont corrects. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Erreur : %@ Post-quantum E2EE + E2EE post-quantique No comment provided by engineer. @@ -4334,10 +4386,12 @@ Erreur : %@ Push server + Serveur Push No comment provided by engineer. Quantum resistant encryption + Chiffrement résistant post-quantique No comment provided by engineer. @@ -4527,10 +4581,12 @@ Erreur : %@ Repeat download + Répéter le téléchargement No comment provided by engineer. Repeat import + Répéter l'importation No comment provided by engineer. @@ -4540,6 +4596,7 @@ Erreur : %@ Repeat upload + Répéter l'envoi No comment provided by engineer. @@ -4644,6 +4701,7 @@ Erreur : %@ Safer groups + Groupes plus sûrs No comment provided by engineer. @@ -5013,6 +5071,7 @@ Erreur : %@ Set passphrase + Définir une phrase secrète No comment provided by engineer. @@ -5072,6 +5131,7 @@ Erreur : %@ Show QR code + Afficher le code QR No comment provided by engineer. @@ -5216,6 +5276,7 @@ Erreur : %@ Stop chat + Arrêter le chat No comment provided by engineer. @@ -5260,6 +5321,7 @@ Erreur : %@ Stopping chat + Arrêt du chat No comment provided by engineer. @@ -5511,10 +5573,12 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. This chat is protected by end-to-end encryption. + Cette discussion est protégée par un chiffrement de bout en bout. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Cette discussion est protégée par un chiffrement de bout en bout résistant aux technologies quantiques. E2EE info chat item @@ -5813,6 +5877,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Upload failed + Échec de l'envoi No comment provided by engineer. @@ -5822,6 +5887,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Uploading archive + Envoi de l'archive No comment provided by engineer. @@ -5876,6 +5942,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use the app while in the call. + Utiliser l'application pendant l'appel. No comment provided by engineer. @@ -5915,10 +5982,12 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Verify database passphrase + Vérifier la phrase secrète de la base de données No comment provided by engineer. Verify passphrase + Vérifier la phrase secrète No comment provided by engineer. @@ -6013,6 +6082,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Attention: démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages No comment provided by engineer. @@ -6037,6 +6107,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Welcome message is too long + Le message de bienvenue est trop long No comment provided by engineer. @@ -6096,6 +6167,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien You **must not** use the same database on two devices. + Vous **ne devez pas** utiliser la même base de données sur deux appareils. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Répéter la demande d'adhésion ? You can give another try. + Vous pouvez faire un nouvel essai. No comment provided by engineer. @@ -7094,6 +7167,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. quantum resistant e2e encryption + chiffrement e2e résistant post-quantique chat item text @@ -7173,6 +7247,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. standard end-to-end encryption + chiffrement de bout en bout standard chat item text diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index ac71ed26bc..45cfe0c468 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -5311,6 +5311,11 @@ SimpleX servers cannot see your profile. ללא היסטוריה No comment provided by engineer. + + %@ and %@ + %@ ו-%@ + No comment provided by engineer. + diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 30467415cf..34d39c405d 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ scaricati No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ caricati No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Nota bene**: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Attenzione**: l'archivio verrà rimosso. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Gli amministratori possono bloccare un membro per tutti. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Migrazione dati dell'app No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Applica No comment provided by engineer. Archive and upload + Archivia e carica No comment provided by engineer. Archiving database + Archiviazione del database No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Annulla migrazione No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Chat migrata! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Scegli _Migra da un altro dispositivo_ sul nuovo dispositivo e scansione il codice QR. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Conferma le impostazioni di rete No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Conferma che ricordi la password del database da migrare. No comment provided by engineer. Confirm upload + Conferma caricamento No comment provided by engineer. @@ -1544,6 +1560,7 @@ Questo è il tuo link una tantum! Creating archive link + Creazione link dell'archivio No comment provided by engineer. @@ -1768,6 +1785,7 @@ Non è reversibile! Delete database from this device + Elimina il database da questo dispositivo No comment provided by engineer. @@ -2062,6 +2080,7 @@ Non è reversibile! Download failed + Scaricamento fallito No comment provided by engineer. @@ -2071,10 +2090,12 @@ Non è reversibile! Downloading archive + Scaricamento archivio No comment provided by engineer. Downloading link details + Scaricamento dettagli del link No comment provided by engineer. @@ -2134,6 +2155,7 @@ Non è reversibile! Enable in direct chats (BETA)! + Attivala nelle chat dirette (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ Non è reversibile! Enter passphrase + Inserisci password No comment provided by engineer. @@ -2317,6 +2340,7 @@ Non è reversibile! Error allowing contact PQ encryption + Errore nel consentire la crittografia PQ al contatto No comment provided by engineer. @@ -2411,6 +2435,7 @@ Non è reversibile! Error downloading the archive + Errore di scaricamento dell'archivio No comment provided by engineer. @@ -2490,6 +2515,7 @@ Non è reversibile! Error saving settings + Errore di salvataggio delle impostazioni when migrating @@ -2564,10 +2590,12 @@ Non è reversibile! Error uploading the archive + Errore di invio dell'archivio No comment provided by engineer. Error verifying passphrase: + Errore di verifica della password: No comment provided by engineer. @@ -2622,6 +2650,7 @@ Non è reversibile! Exported file doesn't exist + Il file esportato non esiste No comment provided by engineer. @@ -2696,10 +2725,12 @@ Non è reversibile! Finalize migration + Finalizza la migrazione No comment provided by engineer. Finalize migration on another device. + Finalizza la migrazione su un altro dispositivo. No comment provided by engineer. @@ -2994,6 +3025,7 @@ Non è reversibile! Hungarian interface + Interfaccia in ungherese No comment provided by engineer. @@ -3063,10 +3095,12 @@ Non è reversibile! Import failed + Importazione fallita No comment provided by engineer. Importing archive + Importazione archivio No comment provided by engineer. @@ -3086,6 +3120,7 @@ Non è reversibile! In order to continue, chat should be stopped. + Per continuare, la chat deve essere fermata. No comment provided by engineer. @@ -3202,6 +3237,7 @@ Non è reversibile! Invalid migration confirmation + Conferma di migrazione non valida No comment provided by engineer. @@ -3574,6 +3610,7 @@ Questo è il tuo link per il gruppo %@! Message too large + Messaggio troppo grande No comment provided by engineer. @@ -3593,34 +3630,42 @@ Questo è il tuo link per il gruppo %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione. No comment provided by engineer. Migrate device + Migra dispositivo No comment provided by engineer. Migrate from another device + Migra da un altro dispositivo No comment provided by engineer. Migrate here + Migra qui No comment provided by engineer. Migrate to another device + Migra ad un altro dispositivo No comment provided by engineer. Migrate to another device via QR code. + Migra ad un altro dispositivo via codice QR. No comment provided by engineer. Migrating + Migrazione No comment provided by engineer. @@ -3630,6 +3675,7 @@ Questo è il tuo link per il gruppo %@! Migration complete + Migrazione completata No comment provided by engineer. @@ -3993,6 +4039,7 @@ Questo è il tuo link per il gruppo %@! Open migration to another device + Apri migrazione ad un altro dispositivo authentication reason @@ -4012,6 +4059,7 @@ Questo è il tuo link per il gruppo %@! Or paste archive link + O incolla il link dell'archivio No comment provided by engineer. @@ -4021,6 +4069,7 @@ Questo è il tuo link per il gruppo %@! Or securely share this file link + O condividi in modo sicuro questo link del file No comment provided by engineer. @@ -4110,6 +4159,7 @@ Questo è il tuo link per il gruppo %@! Picture-in-picture calls + Chiamate picture-in-picture No comment provided by engineer. @@ -4134,6 +4184,7 @@ Questo è il tuo link per il gruppo %@! Please confirm that network settings are correct for this device. + Conferma che le impostazioni di rete sono corrette per questo dispositivo. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Errore: %@ Post-quantum E2EE + E2EE post-quantistica No comment provided by engineer. @@ -4334,10 +4386,12 @@ Errore: %@ Push server + Server push No comment provided by engineer. Quantum resistant encryption + Crittografia resistente alla quantistica No comment provided by engineer. @@ -4527,10 +4581,12 @@ Errore: %@ Repeat download + Ripeti scaricamento No comment provided by engineer. Repeat import + Ripeti importazione No comment provided by engineer. @@ -4540,6 +4596,7 @@ Errore: %@ Repeat upload + Ripeti caricamento No comment provided by engineer. @@ -4644,6 +4701,7 @@ Errore: %@ Safer groups + Gruppi più sicuri No comment provided by engineer. @@ -5013,6 +5071,7 @@ Errore: %@ Set passphrase + Imposta password No comment provided by engineer. @@ -5072,6 +5131,7 @@ Errore: %@ Show QR code + Mostra codice QR No comment provided by engineer. @@ -5216,6 +5276,7 @@ Errore: %@ Stop chat + Ferma la chat No comment provided by engineer. @@ -5260,6 +5321,7 @@ Errore: %@ Stopping chat + Arresto della chat No comment provided by engineer. @@ -5511,10 +5573,12 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. This chat is protected by end-to-end encryption. + Questa chat è protetta da crittografia end-to-end. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Questa chat è protetta da crittografia end-to-end resistente alla quantistica. E2EE info chat item @@ -5813,6 +5877,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Upload failed + Invio fallito No comment provided by engineer. @@ -5822,6 +5887,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Uploading archive + Invio dell'archivio No comment provided by engineer. @@ -5876,6 +5942,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use the app while in the call. + Usa l'app mentre sei in chiamata. No comment provided by engineer. @@ -5915,10 +5982,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Verify database passphrase + Verifica password del database No comment provided by engineer. Verify passphrase + Verifica password No comment provided by engineer. @@ -6013,6 +6082,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi No comment provided by engineer. @@ -6037,6 +6107,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Welcome message is too long + Il messaggio di benvenuto è troppo lungo No comment provided by engineer. @@ -6096,6 +6167,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e You **must not** use the same database on two devices. + **Non devi** usare lo stesso database su due dispositivi. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Ripetere la richiesta di ingresso? You can give another try. + Puoi fare un altro tentativo. No comment provided by engineer. @@ -7094,6 +7167,7 @@ I server di SimpleX non possono vedere il tuo profilo. quantum resistant e2e encryption + crittografia e2e resistente alla quantistica chat item text @@ -7173,6 +7247,7 @@ I server di SimpleX non possono vedere il tuo profilo. standard end-to-end encryption + crittografia end-to-end standard chat item text diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 105552ec84..d450d228d8 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ gedownload No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ geüpload No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Let op**: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Waarschuwing**: het archief wordt verwijderd. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Beheerders kunnen een lid voor iedereen blokkeren. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Migratie van app-gegevens No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Toepassen No comment provided by engineer. Archive and upload + Archiveren en uploaden No comment provided by engineer. Archiving database + Database archiveren No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Migratie annuleren No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Chat gemigreerd! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Kies _Migreren vanaf een ander apparaat_ op het nieuwe apparaat en scan de QR-code. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Bevestig netwerk instellingen No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren. No comment provided by engineer. Confirm upload + Bevestig het uploaden No comment provided by engineer. @@ -1544,6 +1560,7 @@ Dit is uw eigen eenmalige link! Creating archive link + Archief link maken No comment provided by engineer. @@ -1768,6 +1785,7 @@ Dit kan niet ongedaan gemaakt worden! Delete database from this device + Verwijder de database van dit apparaat No comment provided by engineer. @@ -2062,6 +2080,7 @@ Dit kan niet ongedaan gemaakt worden! Download failed + Download mislukt No comment provided by engineer. @@ -2071,10 +2090,12 @@ Dit kan niet ongedaan gemaakt worden! Downloading archive + Archief downloaden No comment provided by engineer. Downloading link details + Link gegevens downloaden No comment provided by engineer. @@ -2134,6 +2155,7 @@ Dit kan niet ongedaan gemaakt worden! Enable in direct chats (BETA)! + Activeer in directe chats (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ Dit kan niet ongedaan gemaakt worden! Enter passphrase + Voer het wachtwoord in No comment provided by engineer. @@ -2317,6 +2340,7 @@ Dit kan niet ongedaan gemaakt worden! Error allowing contact PQ encryption + Fout bij het toestaan van contact PQ-versleuteling No comment provided by engineer. @@ -2411,6 +2435,7 @@ Dit kan niet ongedaan gemaakt worden! Error downloading the archive + Fout bij het downloaden van het archief No comment provided by engineer. @@ -2490,6 +2515,7 @@ Dit kan niet ongedaan gemaakt worden! Error saving settings + Fout bij opslaan van instellingen when migrating @@ -2564,10 +2590,12 @@ Dit kan niet ongedaan gemaakt worden! Error uploading the archive + Fout bij het uploaden van het archief No comment provided by engineer. Error verifying passphrase: + Fout bij het verifiëren van het wachtwoord: No comment provided by engineer. @@ -2622,6 +2650,7 @@ Dit kan niet ongedaan gemaakt worden! Exported file doesn't exist + Geëxporteerd bestand bestaat niet No comment provided by engineer. @@ -2696,10 +2725,12 @@ Dit kan niet ongedaan gemaakt worden! Finalize migration + Voltooi de migratie No comment provided by engineer. Finalize migration on another device. + Voltooi de migratie op een ander apparaat. No comment provided by engineer. @@ -2994,6 +3025,7 @@ Dit kan niet ongedaan gemaakt worden! Hungarian interface + Hongaarse interface No comment provided by engineer. @@ -3063,10 +3095,12 @@ Dit kan niet ongedaan gemaakt worden! Import failed + Importeren is mislukt No comment provided by engineer. Importing archive + Archief importeren No comment provided by engineer. @@ -3086,6 +3120,7 @@ Dit kan niet ongedaan gemaakt worden! In order to continue, chat should be stopped. + Om verder te kunnen gaan, moet de chat worden gestopt. No comment provided by engineer. @@ -3202,6 +3237,7 @@ Dit kan niet ongedaan gemaakt worden! Invalid migration confirmation + Ongeldige migratie bevestiging No comment provided by engineer. @@ -3574,6 +3610,7 @@ Dit is jouw link voor groep %@! Message too large + Bericht te groot No comment provided by engineer. @@ -3593,34 +3630,42 @@ Dit is jouw link voor groep %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. No comment provided by engineer. Migrate device + Apparaat migreren No comment provided by engineer. Migrate from another device + Migreer vanaf een ander apparaat No comment provided by engineer. Migrate here + Migreer hierheen No comment provided by engineer. Migrate to another device + Migreer naar een ander apparaat No comment provided by engineer. Migrate to another device via QR code. + Migreer naar een ander apparaat via QR-code. No comment provided by engineer. Migrating + Migreren No comment provided by engineer. @@ -3630,6 +3675,7 @@ Dit is jouw link voor groep %@! Migration complete + Migratie voltooid No comment provided by engineer. @@ -3978,7 +4024,7 @@ Dit is jouw link voor groep %@! Open chat - Gesprekken openen + Chat openen No comment provided by engineer. @@ -3993,6 +4039,7 @@ Dit is jouw link voor groep %@! Open migration to another device + Open de migratie naar een ander apparaat authentication reason @@ -4012,6 +4059,7 @@ Dit is jouw link voor groep %@! Or paste archive link + Of plak de archief link No comment provided by engineer. @@ -4021,6 +4069,7 @@ Dit is jouw link voor groep %@! Or securely share this file link + Of deel deze bestands link veilig No comment provided by engineer. @@ -4110,6 +4159,7 @@ Dit is jouw link voor groep %@! Picture-in-picture calls + Beeld-in-beeld oproepen No comment provided by engineer. @@ -4134,6 +4184,7 @@ Dit is jouw link voor groep %@! Please confirm that network settings are correct for this device. + Controleer of de netwerk instellingen correct zijn voor dit apparaat. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Fout: %@ Post-quantum E2EE + Post-quantum E2EE No comment provided by engineer. @@ -4334,10 +4386,12 @@ Fout: %@ Push server + Push server No comment provided by engineer. Quantum resistant encryption + quantum bestendige encryptie No comment provided by engineer. @@ -4527,10 +4581,12 @@ Fout: %@ Repeat download + Herhaal het downloaden No comment provided by engineer. Repeat import + Herhaal import No comment provided by engineer. @@ -4540,6 +4596,7 @@ Fout: %@ Repeat upload + Herhaal het uploaden No comment provided by engineer. @@ -4644,6 +4701,7 @@ Fout: %@ Safer groups + Veiligere groepen No comment provided by engineer. @@ -5013,6 +5071,7 @@ Fout: %@ Set passphrase + Wachtwoord instellen No comment provided by engineer. @@ -5072,6 +5131,7 @@ Fout: %@ Show QR code + Toon QR-code No comment provided by engineer. @@ -5216,6 +5276,7 @@ Fout: %@ Stop chat + Stop chat No comment provided by engineer. @@ -5260,6 +5321,7 @@ Fout: %@ Stopping chat + Chat stoppen No comment provided by engineer. @@ -5511,10 +5573,12 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. This chat is protected by end-to-end encryption. + Deze chat is beveiligd met end-to-end codering. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Deze chat wordt beschermd door quantum bestendige end-to-end codering. E2EE info chat item @@ -5813,6 +5877,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Upload failed + Upload mislukt No comment provided by engineer. @@ -5822,6 +5887,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Uploading archive + Archief uploaden No comment provided by engineer. @@ -5876,6 +5942,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use the app while in the call. + Gebruik de app tijdens het gesprek. No comment provided by engineer. @@ -5915,10 +5982,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Verify database passphrase + Controleer het wachtwoord van de database No comment provided by engineer. Verify passphrase + Controleer het wachtwoord No comment provided by engineer. @@ -6013,6 +6082,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten No comment provided by engineer. @@ -6037,6 +6107,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Welcome message is too long + Welkomstbericht is te lang No comment provided by engineer. @@ -6096,6 +6167,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You **must not** use the same database on two devices. + U **mag** niet dezelfde database op twee apparaten gebruiken. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Deelnameverzoek herhalen? You can give another try. + Je kunt het nog een keer proberen. No comment provided by engineer. @@ -7094,6 +7167,7 @@ SimpleX servers kunnen uw profiel niet zien. quantum resistant e2e encryption + quantum bestendige e2e-codering chat item text @@ -7173,6 +7247,7 @@ SimpleX servers kunnen uw profiel niet zien. standard end-to-end encryption + standaard end-to-end encryptie chat item text diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 24e9d5dda2..f4d10b0dc0 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ pobrane No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ wgrane No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + *Uwaga*: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Ostrzeżenie**: archiwum zostanie usunięte. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Administratorzy mogą blokować członka dla wszystkich. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Migracja danych aplikacji No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Zastosuj No comment provided by engineer. Archive and upload + Archiwizuj i prześlij No comment provided by engineer. Archiving database + Archiwizowanie bazy danych No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Anuluj migrację No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Czat zmigrowany! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Wybierz _Zmigruj z innego urządzenia_ na nowym urządzeniu i zeskanuj kod QR. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Potwierdź ustawienia sieciowe No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować. No comment provided by engineer. Confirm upload + Potwierdź wgranie No comment provided by engineer. @@ -1544,6 +1560,7 @@ To jest twój jednorazowy link! Creating archive link + Tworzenie linku archiwum No comment provided by engineer. @@ -1768,6 +1785,7 @@ To nie może być cofnięte! Delete database from this device + Usuń bazę danych z tego urządzenia No comment provided by engineer. @@ -2062,6 +2080,7 @@ To nie może być cofnięte! Download failed + Pobieranie nie udane No comment provided by engineer. @@ -2071,10 +2090,12 @@ To nie może być cofnięte! Downloading archive + Pobieranie archiwum No comment provided by engineer. Downloading link details + Pobieranie szczegółów linku No comment provided by engineer. @@ -2134,6 +2155,7 @@ To nie może być cofnięte! Enable in direct chats (BETA)! + Włącz w czatach bezpośrednich (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ To nie może być cofnięte! Enter passphrase + Wprowadź hasło No comment provided by engineer. @@ -2317,6 +2340,7 @@ To nie może być cofnięte! Error allowing contact PQ encryption + Błąd pozwalania kontaktowi na szyfrowanie PQ No comment provided by engineer. @@ -2411,6 +2435,7 @@ To nie może być cofnięte! Error downloading the archive + Błąd pobierania archiwum No comment provided by engineer. @@ -2490,6 +2515,7 @@ To nie może być cofnięte! Error saving settings + Błąd zapisywania ustawień when migrating @@ -2564,10 +2590,12 @@ To nie może być cofnięte! Error uploading the archive + Błąd wgrywania archiwum No comment provided by engineer. Error verifying passphrase: + Błąd weryfikowania hasła: No comment provided by engineer. @@ -2622,6 +2650,7 @@ To nie może być cofnięte! Exported file doesn't exist + Wyeksportowany plik nie istnieje No comment provided by engineer. @@ -2696,10 +2725,12 @@ To nie może być cofnięte! Finalize migration + Dokończ migrację No comment provided by engineer. Finalize migration on another device. + Dokończ migrację na innym urządzeniu. No comment provided by engineer. @@ -2994,6 +3025,7 @@ To nie może być cofnięte! Hungarian interface + Węgierski interfejs No comment provided by engineer. @@ -3063,10 +3095,12 @@ To nie może być cofnięte! Import failed + Import nie udał się No comment provided by engineer. Importing archive + Importowanie archiwum No comment provided by engineer. @@ -3086,6 +3120,7 @@ To nie może być cofnięte! In order to continue, chat should be stopped. + Aby konturować, czat musi zostać zatrzymany. No comment provided by engineer. @@ -3202,6 +3237,7 @@ To nie może być cofnięte! Invalid migration confirmation + Nieprawidłowe potwierdzenie migracji No comment provided by engineer. @@ -3574,6 +3610,7 @@ To jest twój link do grupy %@! Message too large + Wiadomość jest zbyt duża No comment provided by engineer. @@ -3593,34 +3630,42 @@ To jest twój link do grupy %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. No comment provided by engineer. Migrate device + Zmigruj urządzenie No comment provided by engineer. Migrate from another device + Zmigruj z innego urządzenia No comment provided by engineer. Migrate here + Zmigruj tutaj No comment provided by engineer. Migrate to another device + Zmigruj do innego urządzenia No comment provided by engineer. Migrate to another device via QR code. + Zmigruj do innego urządzenia przez kod QR. No comment provided by engineer. Migrating + Migrowanie No comment provided by engineer. @@ -3630,6 +3675,7 @@ To jest twój link do grupy %@! Migration complete + Migracja zakończona No comment provided by engineer. @@ -3993,6 +4039,7 @@ To jest twój link do grupy %@! Open migration to another device + Otwórz migrację na innym urządzeniu authentication reason @@ -4012,6 +4059,7 @@ To jest twój link do grupy %@! Or paste archive link + Lub wklej link archiwum No comment provided by engineer. @@ -4021,6 +4069,7 @@ To jest twój link do grupy %@! Or securely share this file link + Lub bezpiecznie udostępnij ten link pliku No comment provided by engineer. @@ -4110,6 +4159,7 @@ To jest twój link do grupy %@! Picture-in-picture calls + Połączenia obraz-w-obrazie No comment provided by engineer. @@ -4134,6 +4184,7 @@ To jest twój link do grupy %@! Please confirm that network settings are correct for this device. + Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Błąd: %@ Post-quantum E2EE + Postkwantowe E2EE No comment provided by engineer. @@ -4334,10 +4386,12 @@ Błąd: %@ Push server + Serwer Push No comment provided by engineer. Quantum resistant encryption + Kwantowo odporne szyfrowanie No comment provided by engineer. @@ -4527,10 +4581,12 @@ Błąd: %@ Repeat download + Powtórz pobieranie No comment provided by engineer. Repeat import + Powtórz importowanie No comment provided by engineer. @@ -4540,6 +4596,7 @@ Błąd: %@ Repeat upload + Powtórz wgrywanie No comment provided by engineer. @@ -4644,6 +4701,7 @@ Błąd: %@ Safer groups + Bezpieczniejsze grupy No comment provided by engineer. @@ -5013,6 +5071,7 @@ Błąd: %@ Set passphrase + Ustaw hasło No comment provided by engineer. @@ -5072,6 +5131,7 @@ Błąd: %@ Show QR code + Pokaż kod QR No comment provided by engineer. @@ -5216,6 +5276,7 @@ Błąd: %@ Stop chat + Zatrzymaj czat No comment provided by engineer. @@ -5260,6 +5321,7 @@ Błąd: %@ Stopping chat + Zatrzymywanie czatu No comment provided by engineer. @@ -5511,10 +5573,12 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom This chat is protected by end-to-end encryption. + Ten czat jest chroniony przez szyfrowanie end-to-end. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end. E2EE info chat item @@ -5813,6 +5877,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Upload failed + Wgrywanie nie udane No comment provided by engineer. @@ -5822,6 +5887,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Uploading archive + Wgrywanie archiwum No comment provided by engineer. @@ -5876,6 +5942,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use the app while in the call. + Używaj aplikacji podczas połączenia. No comment provided by engineer. @@ -5915,10 +5982,12 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Verify database passphrase + Zweryfikuj hasło bazy danych No comment provided by engineer. Verify passphrase + Zweryfikuj hasło No comment provided by engineer. @@ -6013,6 +6082,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości No comment provided by engineer. @@ -6037,6 +6107,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Welcome message is too long + Wiadomość powitalna jest zbyt długa No comment provided by engineer. @@ -6096,6 +6167,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc You **must not** use the same database on two devices. + **Nie możesz** używać tej samej bazy na dwóch urządzeniach. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Powtórzyć prośbę dołączenia? You can give another try. + Możesz spróbować ponownie. No comment provided by engineer. @@ -7094,6 +7167,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. quantum resistant e2e encryption + kwantowo odporne szyfrowanie e2e chat item text @@ -7173,6 +7247,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. standard end-to-end encryption + standardowe szyfrowanie end-to-end chat item text diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index 71c523ca4e..a31d4e314d 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -15,32 +15,39 @@ Available in v5.1 No comment provided by engineer. - + + No comment provided by engineer. - + + No comment provided by engineer. - + + No comment provided by engineer. - + ( + ( No comment provided by engineer. - + (can be copied) + .(pode ser copiado) No comment provided by engineer. - + !1 colored! + !1 colorido! No comment provided by engineer. - + #secret# + #secreto# No comment provided by engineer. @@ -55,48 +62,59 @@ Available in v5.1 %@ / %@ No comment provided by engineer. - + %@ is connected! + %@ está conectado! notification title - + %@ is not verified + %@ não foi verificado No comment provided by engineer. - + %@ is verified + %@ foi verificado No comment provided by engineer. - + %@ servers + %@ servidores No comment provided by engineer. - + %@ wants to connect! + %@ quer se conectar! notification title - + %d days + %d dias message ttl - + %d hours + %d horas message ttl - + %d min + %d minuto message ttl - + %d months + %d meses message ttl - + %d sec + %d segundo message ttl - + %d skipped message(s) + %d mensagem(s) ignorada(s) integrity error chat item @@ -107,28 +125,34 @@ Available in v5.1 %lld %@ No comment provided by engineer. - + %lld contact(s) selected + %lld contato(s) selecionado(s) No comment provided by engineer. - + %lld file(s) with total size of %@ + %lld arquivo(s) com tamanho total de %@ No comment provided by engineer. - + %lld members + %lld membros No comment provided by engineer. - + %lld minutes + %lld minutos No comment provided by engineer. - + %lld second(s) + %lld segundo(s) No comment provided by engineer. - + %lld seconds + %lld segundos No comment provided by engineer. @@ -159,12 +183,14 @@ Available in v5.1 %lldw No comment provided by engineer. - + %u messages failed to decrypt. + %u mensagens não foram descriptografadas. No comment provided by engineer. - + %u messages skipped. + %u mensagens ignoradas. No comment provided by engineer. @@ -175,48 +201,56 @@ Available in v5.1 ) No comment provided by engineer. - + **Add new contact**: to create your one-time QR Code or link for your contact. + **Adicionar novo contato**: para criar seu QR Code único ou link para seu contato. No comment provided by engineer. **Create link / QR code** for your contact to use. No comment provided by engineer. - + **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + **Mais privado**: verifique novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor SimpleX Chat, mas não com quantos contatos ou mensagens você possui. No comment provided by engineer. - + **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + **Totalmente privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo). No comment provided by engineer. **Paste received link** or open it in the browser and tap **Open in mobile app**. No comment provided by engineer. - + **Please note**: you will NOT be able to recover or change passphrase if you lose it. + **Atenção**: Você NÃO poderá recuperar ou alterar a senha caso a perca. No comment provided by engineer. - + **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + **Recomendado**: O token do dispositivo e as notificações são enviados ao servidor de notificação do SimpleX Chat, mas não o conteúdo, o tamanho da mensagem ou de quem ela é. No comment provided by engineer. **Scan QR code**: to connect to your contact in person or via video call. No comment provided by engineer. - + **Warning**: Instant push notifications require passphrase saved in Keychain. + **Aviso**: As notificações push instantâneas exigem uma senha salva nas Chaves. No comment provided by engineer. - + **e2e encrypted** audio call + ** Criptografado e2e** chamada de áudio No comment provided by engineer. - + **e2e encrypted** video call + **Criptografado e2e** chamada de vídeo No comment provided by engineer. @@ -353,144 +387,175 @@ Available in v5.1 All group members will remain connected. No comment provided by engineer. - + All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. + Todas as mensagens serão apagadas – isso não pode ser desfeito! As mensagens serão apagadas SOMENTE para você. No comment provided by engineer. All your contacts will remain connected No comment provided by engineer. - + Allow + Permitir No comment provided by engineer. - + Allow calls only if your contact allows them. + Permita chamadas somente se seu contato permitir. No comment provided by engineer. - + Allow disappearing messages only if your contact allows it to you. + Permita o desaparecimento de mensagens somente se o seu contato permitir. No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. No comment provided by engineer. - + Allow sending direct messages to members. + Permitir o envio de mensagens diretas aos membros. No comment provided by engineer. - + Allow sending disappearing messages. + Permitir o envio de mensagens que desaparecem. No comment provided by engineer. Allow to irreversibly delete sent messages. No comment provided by engineer. - + Allow to send voice messages. + Permitir enviar mensagens de voz. No comment provided by engineer. - + Allow voice messages only if your contact allows them. + Permita mensagens de voz apenas se o seu contato permitir. No comment provided by engineer. - + Allow voice messages? + Permitir mensagens de voz? No comment provided by engineer. - + Allow your contacts to call you. + Permita que seus contatos liguem para você. No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. No comment provided by engineer. - + Allow your contacts to send disappearing messages. + Permita que seus contatos enviem mensagens que desaparecem. No comment provided by engineer. - + Allow your contacts to send voice messages. + Permita que seus contatos enviem mensagens de voz. No comment provided by engineer. - + Already connected? + Já conectado? No comment provided by engineer. - + Always use relay + Sempre usar retransmissão No comment provided by engineer. - + Answer call + Atender chamada No comment provided by engineer. - + App build: %@ + Versão do Aplicativo: %@ No comment provided by engineer. - + App icon + Ícone do Aplicativo No comment provided by engineer. - + App passcode + Senha do Aplicativo No comment provided by engineer. - + App version + Versão do Aplicativo No comment provided by engineer. - + App version: v%@ + Versão do Aplicativo: v%@ No comment provided by engineer. - + Appearance + Aparência No comment provided by engineer. - + Attach + Anexar No comment provided by engineer. - + Audio & video calls + Chamadas de áudio e vídeo No comment provided by engineer. - + Audio and video calls + Chamadas de áudio e vídeo No comment provided by engineer. - + Audio/video calls + Chamadas de áudio/vídeo chat feature - + Audio/video calls are prohibited. + Chamadas de áudio/vídeo são proibidas. No comment provided by engineer. - + Authentication cancelled + Autenticação cancelada PIN entry - + Authentication failed + Falha na autenticação No comment provided by engineer. - + Authentication is required before the call is connected, but you may miss calls. + A autenticação é necessária antes que a chamada seja conectada, mas você pode perder chamadas. No comment provided by engineer. - + Authentication unavailable + Autenticação indisponível No comment provided by engineer. - + Auto-accept contact requests + Aceitar solicitações de contato automaticamente No comment provided by engineer. @@ -4162,6 +4227,226 @@ SimpleX servers cannot see your profile. \~strike~ No comment provided by engineer. + + ## In reply to + ## Em resposta a + copied message info + + + %@ uploaded + %@ enviado + No comment provided by engineer. + + + %d weeks + %d semanas + time interval + + + %lld messages blocked by admin + %lld mensagens bloqueadas pelo administrador + No comment provided by engineer. + + + %lld new interface languages + %lld novas interface de idiomas + No comment provided by engineer. + + + **Add contact**: to create a new invitation link, or connect via a link you received. + **Adicionar contato**: para criar um novo link de convite ou conectar-se por meio de um link que você recebeu. + No comment provided by engineer. + + + **Create group**: to create a new group. + **Criar grupo**: para criar um novo grupo. + No comment provided by engineer. + + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Observação**: usar o mesmo banco de dados em dois dispositivos interromperá a descriptografia de mensagens de suas conexões, como proteção de segurança. + No comment provided by engineer. + + + ## History + ## Histórico + copied message info + + + **Warning**: the archive will be removed. + **Atenção**: O arquivo será removido. + No comment provided by engineer. + + + %@ and %@ + %@ e %@ + No comment provided by engineer. + + + %@ and %@ connected + %@ e %@ conectado + No comment provided by engineer. + + + %1$@ at %2$@: + %1$@ em %2$@: + copied message info, <sender> at <time> + + + %@ connected + %@ conectado + No comment provided by engineer. + + + %@, %@ and %lld members + %@, %@ e %lld membros + No comment provided by engineer. + + + %@, %@ and %lld other members connected + %@, %@ e %lld outros membros conectados + No comment provided by engineer. + + + %lld group events + %lld eventos de grupo + No comment provided by engineer. + + + %lld messages marked deleted + %lld mensagens marcadas como excluídas + No comment provided by engineer. + + + %lld messages blocked + %lld mensagens bloqueadas + No comment provided by engineer. + + + %lld messages moderated by %@ + %lld mensagens moderadas por %@ + No comment provided by engineer. + + + (this device v%@) + (este dispositivo v%@) + No comment provided by engineer. + + + All data is erased when it is entered. + Todos os dados são apagados quando são inseridos. + No comment provided by engineer. + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + Permita a exclusão irreversível de mensagens somente se o seu contato permitir. (24 horas) + No comment provided by engineer. + + + Apply + Aplicar + No comment provided by engineer. + + + Archive and upload + Arquivar e fazer envio + No comment provided by engineer. + + + All new messages from %@ will be hidden! + Todas as novas mensagens de %@ ficarão ocultas! + No comment provided by engineer. + + + Already connecting! + Já conectando! + No comment provided by engineer. + + + App encrypts new local files (except videos). + O aplicativo criptografa novos arquivos locais (exceto vídeos). + No comment provided by engineer. + + + App passcode is replaced with self-destruct passcode. + A senha do Aplicativo é substituída pela senha de autodestruição. + No comment provided by engineer. + + + All your contacts will remain connected. + Todos os seus contatos permanecerão conectados. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. + Todos os seus contatos permanecerão conectados. A atualização do perfil será enviada para seus contatos. + No comment provided by engineer. + + + Allow to send files and media. + Permitir o envio de arquivos e mídia. + No comment provided by engineer. + + + App data migration + Migração de dados de aplicativos + No comment provided by engineer. + + + Archiving database + Arquivando banco de dados + No comment provided by engineer. + + + All messages will be deleted - this cannot be undone! + Todas as mensagens serão apagadas – isso não pode ser desfeito! + No comment provided by engineer. + + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Todos os seus contatos, conversas e arquivos serão criptografados com segurança e enviados em partes para retransmissões XFTP configuradas. + No comment provided by engineer. + + + Allow message reactions only if your contact allows them. + Permita reações às mensagens somente se o seu contato permitir. + No comment provided by engineer. + + + Allow message reactions. + Permitir reações às mensagens. + No comment provided by engineer. + + + Allow your contacts to irreversibly delete sent messages. (24 hours) + Permita que seus contatos apaguem irreversivelmente as mensagens enviadas. (24 horas) + No comment provided by engineer. + + + Already joining the group! + Entrando no grupo! + No comment provided by engineer. + + + An empty chat profile with the provided name is created, and the app opens as usual. + Um perfil de conversa vazio com o nome fornecido é criado e o aplicativo abre normalmente. + No comment provided by engineer. + + + Auto-accept + Aceitar automaticamente + No comment provided by engineer. + + + Allow your contacts adding message reactions. + Permita que seus contatos adicionem reações às mensagens. + No comment provided by engineer. + + + Allow to irreversibly delete sent messages. (24 hours) + Permitir apagar irreversivelmente as mensagens enviadas. (24 horas) + No comment provided by engineer. + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 2b0bf95f45..7d5f119b9d 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ загружено No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ загружено No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Внимание**: архив будет удален. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Админы могут заблокировать члена группы. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Миграция данных No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Применить No comment provided by engineer. Archive and upload + Архивировать и загрузить No comment provided by engineer. Archiving database + Подготовка архива No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Отменить миграцию No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Чат мигрирован! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Подтвердите настройки сети No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Подтвердите, что Вы помните пароль базы данных для ее миграции. No comment provided by engineer. Confirm upload + Подтвердить загрузку No comment provided by engineer. @@ -1544,6 +1560,7 @@ This is your own one-time link! Creating archive link + Создание ссылки на архив No comment provided by engineer. @@ -1768,6 +1785,7 @@ This cannot be undone! Delete database from this device + Удалить базу данных с этого устройства No comment provided by engineer. @@ -2062,6 +2080,7 @@ This cannot be undone! Download failed + Ошибка загрузки No comment provided by engineer. @@ -2071,10 +2090,12 @@ This cannot be undone! Downloading archive + Загрузка архива No comment provided by engineer. Downloading link details + Загрузка ссылки архива No comment provided by engineer. @@ -2134,6 +2155,7 @@ This cannot be undone! Enable in direct chats (BETA)! + Включите для контактов (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ This cannot be undone! Enter passphrase + Введите пароль No comment provided by engineer. @@ -2317,6 +2340,7 @@ This cannot be undone! Error allowing contact PQ encryption + Ошибка разрешения квантово-устойчивого шифрования No comment provided by engineer. @@ -2411,6 +2435,7 @@ This cannot be undone! Error downloading the archive + Ошибка загрузки архива No comment provided by engineer. @@ -2490,6 +2515,7 @@ This cannot be undone! Error saving settings + Ошибка сохранения настроек when migrating @@ -2564,10 +2590,12 @@ This cannot be undone! Error uploading the archive + Ошибка загрузки архива No comment provided by engineer. Error verifying passphrase: + Ошибка подтверждения пароля: No comment provided by engineer. @@ -2622,6 +2650,7 @@ This cannot be undone! Exported file doesn't exist + Экспортированный файл не существует No comment provided by engineer. @@ -2696,10 +2725,12 @@ This cannot be undone! Finalize migration + Завершить миграцию No comment provided by engineer. Finalize migration on another device. + Завершите миграцию на другом устройстве. No comment provided by engineer. @@ -2994,6 +3025,7 @@ This cannot be undone! Hungarian interface + Венгерский интерфейс No comment provided by engineer. @@ -3063,10 +3095,12 @@ This cannot be undone! Import failed + Ошибка импорта No comment provided by engineer. Importing archive + Импорт архива No comment provided by engineer. @@ -3086,6 +3120,7 @@ This cannot be undone! In order to continue, chat should be stopped. + Чтобы продолжить, чат должен быть остановлен. No comment provided by engineer. @@ -3202,6 +3237,7 @@ This cannot be undone! Invalid migration confirmation + Ошибка подтверждения миграции No comment provided by engineer. @@ -3574,6 +3610,7 @@ This is your link for group %@! Message too large + Сообщение слишком большое No comment provided by engineer. @@ -3593,34 +3630,42 @@ This is your link for group %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. No comment provided by engineer. Migrate device + Мигрировать устройство No comment provided by engineer. Migrate from another device + Миграция с другого устройства No comment provided by engineer. Migrate here + Мигрировать сюда No comment provided by engineer. Migrate to another device + Мигрировать на другое устройство No comment provided by engineer. Migrate to another device via QR code. + Мигрируйте на другое устройство через QR код. No comment provided by engineer. Migrating + Миграция No comment provided by engineer. @@ -3630,6 +3675,7 @@ This is your link for group %@! Migration complete + Миграция завершена No comment provided by engineer. @@ -3993,6 +4039,7 @@ This is your link for group %@! Open migration to another device + Открытие миграции на другое устройство authentication reason @@ -4012,6 +4059,7 @@ This is your link for group %@! Or paste archive link + Или вставьте ссылку архива No comment provided by engineer. @@ -4021,6 +4069,7 @@ This is your link for group %@! Or securely share this file link + Или передайте эту ссылку No comment provided by engineer. @@ -4110,6 +4159,7 @@ This is your link for group %@! Picture-in-picture calls + Звонки с картинкой-в-картинке No comment provided by engineer. @@ -4134,6 +4184,7 @@ This is your link for group %@! Please confirm that network settings are correct for this device. + Пожалуйста, подтвердите, что настройки сети верны для этого устройства. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Error: %@ Post-quantum E2EE + Квантово-устойчивое E2EE No comment provided by engineer. @@ -4334,10 +4386,12 @@ Error: %@ Push server + Сервер уведомлений No comment provided by engineer. Quantum resistant encryption + Квантово-устойчивое шифрование No comment provided by engineer. @@ -4527,10 +4581,12 @@ Error: %@ Repeat download + Повторить загрузку No comment provided by engineer. Repeat import + Повторить импорт No comment provided by engineer. @@ -4540,6 +4596,7 @@ Error: %@ Repeat upload + Повторить загрузку No comment provided by engineer. @@ -4644,6 +4701,7 @@ Error: %@ Safer groups + Более безопасные группы No comment provided by engineer. @@ -5013,6 +5071,7 @@ Error: %@ Set passphrase + Установить пароль No comment provided by engineer. @@ -5072,6 +5131,7 @@ Error: %@ Show QR code + Показать QR код No comment provided by engineer. @@ -5216,6 +5276,7 @@ Error: %@ Stop chat + Остановить чат No comment provided by engineer. @@ -5260,6 +5321,7 @@ Error: %@ Stopping chat + Остановка чата No comment provided by engineer. @@ -5511,10 +5573,12 @@ It can happen because of some bug or when the connection is compromised. This chat is protected by end-to-end encryption. + Чат защищен end-to-end шифрованием. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Чат защищен квантово-устойчивым end-to-end шифрованием. E2EE info chat item @@ -5813,6 +5877,7 @@ To connect, please ask your contact to create another connection link and check Upload failed + Ошибка загрузки No comment provided by engineer. @@ -5822,6 +5887,7 @@ To connect, please ask your contact to create another connection link and check Uploading archive + Загрузка архива No comment provided by engineer. @@ -5876,6 +5942,7 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. + Используйте приложение во время звонка. No comment provided by engineer. @@ -5915,10 +5982,12 @@ To connect, please ask your contact to create another connection link and check Verify database passphrase + Проверка пароля базы данных No comment provided by engineer. Verify passphrase + Проверить пароль No comment provided by engineer. @@ -6013,6 +6082,7 @@ To connect, please ask your contact to create another connection link and check Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений No comment provided by engineer. @@ -6037,6 +6107,7 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long + Приветственное сообщение слишком длинное No comment provided by engineer. @@ -6096,6 +6167,7 @@ To connect, please ask your contact to create another connection link and check You **must not** use the same database on two devices. + Вы **не должны** использовать одну и ту же базу данных на двух устройствах. No comment provided by engineer. @@ -6187,6 +6259,7 @@ Repeat join request? You can give another try. + Вы можете попробовать еще раз. No comment provided by engineer. @@ -7094,6 +7167,7 @@ SimpleX серверы не могут получить доступ к Ваше quantum resistant e2e encryption + квантово-устойчивое e2e шифрование chat item text @@ -7173,6 +7247,7 @@ SimpleX серверы не могут получить доступ к Ваше standard end-to-end encryption + стандартное end-to-end шифрование chat item text diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index d5264200c3..05c68ea21e 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -99,7 +99,7 @@ %1$@ at %2$@: - %1$@, %2$@ de + 1$@, %2$@'de: copied message info, <sender> at <time> @@ -109,6 +109,7 @@ %@ downloaded + %@ indirildi No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ yüklendi No comment provided by engineer. @@ -302,7 +304,7 @@ %u messages skipped. - %u mesaj atlandı + %u mesajlar atlandı. No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Lütfen dikkat**: Aynı veritabanını iki cihazda kullanmak, güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Uyarı**: arşiv silinecektir. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Yöneticiler bir üyeyi tamamen engelleyebilirler. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tüm kişileriniz, sohbetleriniz ve dosyalarınız güvenli bir şekilde şifrelenecek ve parçalar halinde yapılandırılmış XFTP rölelerine yüklenecektir. No comment provided by engineer. @@ -799,7 +805,7 @@ Always use relay - Her zaman yönlendirici kulan + Her zaman yönlendirici kullan No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Uygulama verisi taşıma No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Uygula No comment provided by engineer. Archive and upload + Arşivle ve yükle No comment provided by engineer. Archiving database + Veritabanı arşivleniyor No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Taşımayı iptal et No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Sohbet taşındı! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Yeni cihazda _Başka bir cihazdan taşı_ seçeneğini seçin ve QR kodunu tarayın. No comment provided by engineer. @@ -1263,6 +1276,7 @@ Confirm network settings + Ağ ayarlarını onaylayın No comment provided by engineer. @@ -1277,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Taşımak için veritabanı parolasını hatırladığınızı doğrulayın. No comment provided by engineer. Confirm upload + Yüklemeyi onayla No comment provided by engineer. @@ -1544,6 +1560,7 @@ Bu senin kendi tek kullanımlık bağlantın! Creating archive link + Arşiv bağlantısı oluşturuluyor No comment provided by engineer. @@ -1768,6 +1785,7 @@ Bu geri alınamaz! Delete database from this device + Veritabanını bu cihazdan sil No comment provided by engineer. @@ -2062,6 +2080,7 @@ Bu geri alınamaz! Download failed + Yükleme başarısız oldu No comment provided by engineer. @@ -2071,10 +2090,12 @@ Bu geri alınamaz! Downloading archive + Arşiv indiriliyor No comment provided by engineer. Downloading link details + Bağlantı detayları indiriliyor No comment provided by engineer. @@ -2134,6 +2155,7 @@ Bu geri alınamaz! Enable in direct chats (BETA)! + Doğrudan sohbetlerde etkinleştirin (BETA)! No comment provided by engineer. @@ -2253,6 +2275,7 @@ Bu geri alınamaz! Enter passphrase + Parolayı girin No comment provided by engineer. @@ -2317,6 +2340,7 @@ Bu geri alınamaz! Error allowing contact PQ encryption + İletişim PQ şifrelemesine izin verirken hata oluştu No comment provided by engineer. @@ -2411,6 +2435,7 @@ Bu geri alınamaz! Error downloading the archive + Arşiv indirilirken hata oluştu No comment provided by engineer. @@ -2490,6 +2515,7 @@ Bu geri alınamaz! Error saving settings + Ayarlar kaydedilirken hata oluştu when migrating @@ -2564,10 +2590,12 @@ Bu geri alınamaz! Error uploading the archive + Arşiv yüklenirken hata oluştu No comment provided by engineer. Error verifying passphrase: + Parola doğrulanırken hata oluştu: No comment provided by engineer. @@ -2622,6 +2650,7 @@ Bu geri alınamaz! Exported file doesn't exist + Dışa aktarılan dosya mevcut değil No comment provided by engineer. @@ -2696,10 +2725,12 @@ Bu geri alınamaz! Finalize migration + Taşıma işlemini sonlandır No comment provided by engineer. Finalize migration on another device. + Taşıma işlemini başka bir cihazda sonlandırın. No comment provided by engineer. @@ -2994,6 +3025,7 @@ Bu geri alınamaz! Hungarian interface + Macarca arayüz No comment provided by engineer. @@ -3063,10 +3095,12 @@ Bu geri alınamaz! Import failed + İçe aktarma başarısız oldu No comment provided by engineer. Importing archive + Arşiv içe aktarılıyor No comment provided by engineer. @@ -3086,6 +3120,7 @@ Bu geri alınamaz! In order to continue, chat should be stopped. + Devam etmek için sohbetin durdurulması gerekiyor. No comment provided by engineer. @@ -3202,6 +3237,7 @@ Bu geri alınamaz! Invalid migration confirmation + Geçersiz taşıma onayı No comment provided by engineer. @@ -3574,6 +3610,7 @@ Bu senin grup için bağlantın %@! Message too large + Mesaj çok büyük No comment provided by engineer. @@ -3593,34 +3630,42 @@ Bu senin grup için bağlantın %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Mesajlar, dosyalar ve aramalar **kuantum dirençli e2e şifreleme** ile mükemmel ileri gizlilik, inkar ve zorla girme kurtarma ile korunur. No comment provided by engineer. Migrate device + Cihazı taşıma No comment provided by engineer. Migrate from another device + Başka bir cihazdan geçiş yapın No comment provided by engineer. Migrate here + Buraya göç edin No comment provided by engineer. Migrate to another device + Başka bir cihaza taşıma No comment provided by engineer. Migrate to another device via QR code. + QR kodu aracılığıyla başka bir cihaza geçiş yapın. No comment provided by engineer. Migrating + Göçmenlik No comment provided by engineer. @@ -3630,6 +3675,7 @@ Bu senin grup için bağlantın %@! Migration complete + Geçiş tamamlandı No comment provided by engineer. @@ -3993,6 +4039,7 @@ Bu senin grup için bağlantın %@! Open migration to another device + Başka bir cihaza açık geçiş authentication reason @@ -4012,6 +4059,7 @@ Bu senin grup için bağlantın %@! Or paste archive link + Veya arşiv bağlantısını yapıştırın No comment provided by engineer. @@ -4021,6 +4069,7 @@ Bu senin grup için bağlantın %@! Or securely share this file link + Veya bu dosya bağlantısını güvenli bir şekilde paylaşın No comment provided by engineer. @@ -4110,6 +4159,7 @@ Bu senin grup için bağlantın %@! Picture-in-picture calls + Resim içinde resim aramaları No comment provided by engineer. @@ -4134,6 +4184,7 @@ Bu senin grup için bağlantın %@! Please confirm that network settings are correct for this device. + Lütfen bu cihaz için ağ ayarlarının doğru olduğunu onaylayın. No comment provided by engineer. @@ -4195,6 +4246,7 @@ Hata: %@ Post-quantum E2EE + Kuantum sonrası E2EE No comment provided by engineer. @@ -4334,10 +4386,12 @@ Hata: %@ Push server + Push sunucu No comment provided by engineer. Quantum resistant encryption + Kuantum dirençli şifreleme No comment provided by engineer. @@ -4387,7 +4441,7 @@ Hata: %@ Receipts are disabled - Görüldü devre dışı bırakıldı + Makbuzlar devre dışı bırakıldı No comment provided by engineer. @@ -4527,10 +4581,12 @@ Hata: %@ Repeat download + Tekrar indir No comment provided by engineer. Repeat import + İthalatı tekrarla No comment provided by engineer. @@ -4540,6 +4596,7 @@ Hata: %@ Repeat upload + Yüklemeyi tekrarla No comment provided by engineer. @@ -4644,6 +4701,7 @@ Hata: %@ Safer groups + Daha güvenli gruplar No comment provided by engineer. @@ -4903,7 +4961,7 @@ Hata: %@ Sending delivery receipts will be enabled for all contacts. - Görüldü bilgisi bütün kişileri için etkinleştirilecektir. + Teslimat makbuzlarının gönderilmesi tüm kişiler için etkinleştirilecektir. No comment provided by engineer. @@ -5013,6 +5071,7 @@ Hata: %@ Set passphrase + Parolayı ayarla No comment provided by engineer. @@ -5072,6 +5131,7 @@ Hata: %@ Show QR code + QR kodunu göster No comment provided by engineer. @@ -5216,6 +5276,7 @@ Hata: %@ Stop chat + Sohbeti kes No comment provided by engineer. @@ -5260,6 +5321,7 @@ Hata: %@ Stopping chat + Sohbeti durdurma No comment provided by engineer. @@ -5289,7 +5351,7 @@ Hata: %@ TCP_KEEPCNT - TCP_CNTYİTUT + TCP_KEEPCNT No comment provided by engineer. @@ -5511,10 +5573,12 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This chat is protected by end-to-end encryption. + Bu sohbet uçtan uca şifreleme ile korunmaktadır. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Bu sohbet kuantum dirençli uçtan uca şifreleme ile korunmaktadır. E2EE info chat item @@ -5524,8 +5588,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This display name is invalid. Please choose another name. - Görünen ad geçerli değil. - Lütfen başka bir ad seç. + Bu görünen ad geçersiz. Lütfen başka bir isim seçin. No comment provided by engineer. @@ -5814,6 +5877,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Upload failed + Yükleme başarısız No comment provided by engineer. @@ -5823,6 +5887,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uploading archive + Arşiv yükleme No comment provided by engineer. @@ -5877,6 +5942,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use the app while in the call. + Görüşme sırasında uygulamayı kullanın. No comment provided by engineer. @@ -5916,10 +5982,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Verify database passphrase + Veritabanı parolasını doğrulayın No comment provided by engineer. Verify passphrase + Parolayı doğrula No comment provided by engineer. @@ -6014,6 +6082,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Uyarı: birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj teslim hatalarına neden olur No comment provided by engineer. @@ -6038,6 +6107,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Welcome message is too long + Hoş geldiniz mesajı çok uzun No comment provided by engineer. @@ -6097,6 +6167,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste You **must not** use the same database on two devices. + Aynı veritabanını iki cihazda **kullanmamalısınız**. No comment provided by engineer. @@ -6188,6 +6259,7 @@ Katılma isteği tekrarlansın mı? You can give another try. + Bir kez daha deneyebilirsiniz. No comment provided by engineer. @@ -6309,7 +6381,7 @@ Bağlantı isteği tekrarlansın mı? You rejected group invitation - Grup davetini reddettiniz. + Grup davetini reddettiniz No comment provided by engineer. @@ -7095,6 +7167,7 @@ SimpleX sunucuları profilinizi göremez. quantum resistant e2e encryption + kuantuma dayanıklı e2e şifreleme chat item text @@ -7174,6 +7247,7 @@ SimpleX sunucuları profilinizi göremez. standard end-to-end encryption + standart uçtan uca şifreleme chat item text diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 19b46bfc45..29edc0b9ac 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -109,6 +109,7 @@ %@ downloaded + %@ встановлено No comment provided by engineer. @@ -133,6 +134,7 @@ %@ uploaded + %@ завантажено No comment provided by engineer. @@ -352,6 +354,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Зверніть увагу**: використання однієї і тієї ж бази даних на двох пристроях порушить розшифровку повідомлень з ваших з'єднань, як захист безпеки. No comment provided by engineer. @@ -371,6 +374,7 @@ **Warning**: the archive will be removed. + **Попередження**: архів буде видалено. No comment provided by engineer. @@ -631,6 +635,7 @@ Admins can block a member for all. + Адміністратори можуть заблокувати користувача для всіх. No comment provided by engineer. @@ -690,6 +695,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Всі ваші контакти, розмови та файли будуть надійно зашифровані та завантажені частинами на налаштовані XFTP-реле. No comment provided by engineer. @@ -819,6 +825,7 @@ App data migration + Міграція даних додатків No comment provided by engineer. @@ -858,14 +865,17 @@ Apply + Подати заявку No comment provided by engineer. Archive and upload + Архівування та завантаження No comment provided by engineer. Archiving database + Архівування бази даних No comment provided by engineer. @@ -1060,6 +1070,7 @@ Cancel migration + Скасувати міграцію No comment provided by engineer. @@ -1165,6 +1176,7 @@ Chat migrated! + Чат перемістився! No comment provided by engineer. @@ -1189,6 +1201,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + Виберіть _Перемістити з іншого пристрою_ на новому пристрої та відскануйте QR-код. No comment provided by engineer. @@ -1218,6 +1231,7 @@ Clear private notes? + Чисті приватні нотатки? No comment provided by engineer. @@ -1262,6 +1276,7 @@ Confirm network settings + Підтвердьте налаштування мережі No comment provided by engineer. @@ -1276,10 +1291,12 @@ Confirm that you remember database passphrase to migrate it. + Переконайтеся, що ви пам'ятаєте пароль до бази даних для її перенесення. No comment provided by engineer. Confirm upload + Підтвердити завантаження No comment provided by engineer. @@ -1348,6 +1365,7 @@ This is your own one-time link! Connected to desktop + Підключено до настільного комп'ютера No comment provided by engineer. @@ -1362,6 +1380,7 @@ This is your own one-time link! Connecting to desktop + Підключення до ПК No comment provided by engineer. @@ -1386,6 +1405,7 @@ This is your own one-time link! Connection terminated + З'єднання розірвано No comment provided by engineer. @@ -1455,6 +1475,7 @@ This is your own one-time link! Correct name to %@? + Виправити ім'я на %@? No comment provided by engineer. @@ -1469,6 +1490,7 @@ This is your own one-time link! Create a group using a random profile. + Створіть групу, використовуючи випадковий профіль. No comment provided by engineer. @@ -1483,6 +1505,7 @@ This is your own one-time link! Create group + Створити групу No comment provided by engineer. @@ -1497,10 +1520,12 @@ This is your own one-time link! Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 + Створіть новий профіль у [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. Create profile + Створити профіль No comment provided by engineer. @@ -1520,10 +1545,12 @@ This is your own one-time link! Created at + Створено за адресою No comment provided by engineer. Created at: %@ + Створено за адресою: %@ copied message info @@ -1533,10 +1560,12 @@ This is your own one-time link! Creating archive link + Створення архівного посилання No comment provided by engineer. Creating link… + Створення посилання… No comment provided by engineer. @@ -1679,6 +1708,7 @@ This is your own one-time link! Delete %lld messages? + Видалити %lld повідомлень? No comment provided by engineer. @@ -1708,6 +1738,7 @@ This is your own one-time link! Delete and notify contact + Видалити та повідомити контакт No comment provided by engineer. @@ -1743,6 +1774,8 @@ This is your own one-time link! Delete contact? This cannot be undone! + Видалити контакт? +Це не можна скасувати! No comment provided by engineer. @@ -1752,6 +1785,7 @@ This cannot be undone! Delete database from this device + Видалити базу даних з цього пристрою No comment provided by engineer. @@ -1891,14 +1925,17 @@ This cannot be undone! Desktop address + Адреса робочого столу No comment provided by engineer. Desktop app version %@ is not compatible with this app. + Версія програми для настільних комп'ютерів %@ не сумісна з цією програмою. No comment provided by engineer. Desktop devices + Настільні пристрої No comment provided by engineer. @@ -1993,14 +2030,17 @@ This cannot be undone! Disconnect desktop? + Відключити робочий стіл? No comment provided by engineer. Discover and join groups + Знаходьте та приєднуйтесь до груп No comment provided by engineer. Discover via local network + Відкриття через локальну мережу No comment provided by engineer. @@ -2015,6 +2055,7 @@ This cannot be undone! Do not send history to new members. + Не надсилайте історію новим користувачам. No comment provided by engineer. @@ -2039,6 +2080,7 @@ This cannot be undone! Download failed + Не вдалося завантажити No comment provided by engineer. @@ -2048,10 +2090,12 @@ This cannot be undone! Downloading archive + Завантажити архів No comment provided by engineer. Downloading link details + Деталі посилання для завантаження No comment provided by engineer. @@ -2101,6 +2145,7 @@ This cannot be undone! Enable camera access + Увімкніть доступ до камери No comment provided by engineer. @@ -2110,6 +2155,7 @@ This cannot be undone! Enable in direct chats (BETA)! + Увімкнути в прямих чатах (BETA)! No comment provided by engineer. @@ -2154,10 +2200,12 @@ This cannot be undone! Encrypt local files + Шифрування локальних файлів No comment provided by engineer. Encrypt stored files & media + Шифрування збережених файлів і носіїв No comment provided by engineer. @@ -2172,6 +2220,7 @@ This cannot be undone! Encrypted message: app is stopped + Зашифроване повідомлення: додаток зупинено notification @@ -2201,10 +2250,12 @@ This cannot be undone! Encryption re-negotiation error + Помилка повторного узгодження шифрування message decrypt error item Encryption re-negotiation failed. + Повторне узгодження шифрування не вдалося. No comment provided by engineer. @@ -2219,10 +2270,12 @@ This cannot be undone! Enter group name… + Введіть назву групи… No comment provided by engineer. Enter passphrase + Введіть парольну фразу No comment provided by engineer. @@ -2242,6 +2295,7 @@ This cannot be undone! Enter this device name… + Введіть назву пристрою… No comment provided by engineer. @@ -2256,6 +2310,7 @@ This cannot be undone! Enter your name… + Введіть своє ім'я… No comment provided by engineer. @@ -2285,6 +2340,7 @@ This cannot be undone! Error allowing contact PQ encryption + Помилка, що дозволяє шифрування контакту PQ No comment provided by engineer. @@ -2319,10 +2375,12 @@ This cannot be undone! Error creating member contact + Помилка при створенні контакту користувача No comment provided by engineer. Error creating message + Повідомлення про створення помилки No comment provided by engineer. @@ -2332,6 +2390,7 @@ This cannot be undone! Error decrypting file + Помилка розшифрування файлу No comment provided by engineer. @@ -2376,6 +2435,7 @@ This cannot be undone! Error downloading the archive + Помилка завантаження архіву No comment provided by engineer. @@ -2415,6 +2475,7 @@ This cannot be undone! Error opening chat + Помилка відкриття чату No comment provided by engineer. @@ -2454,6 +2515,7 @@ This cannot be undone! Error saving settings + Налаштування збереження помилок when migrating @@ -2463,6 +2525,7 @@ This cannot be undone! Error scanning code: %@ + Код помилки сканування: %@ No comment provided by engineer. @@ -2472,6 +2535,7 @@ This cannot be undone! Error sending member contact invitation + Помилка надсилання запрошення до контактів учасника No comment provided by engineer. @@ -2526,10 +2590,12 @@ This cannot be undone! Error uploading the archive + Помилка при завантаженні архіву No comment provided by engineer. Error verifying passphrase: + Помилка при перевірці парольної фрази: No comment provided by engineer. @@ -2564,6 +2630,7 @@ This cannot be undone! Expand + Розгорнути chat item action @@ -2583,6 +2650,7 @@ This cannot be undone! Exported file doesn't exist + Експортований файл не існує No comment provided by engineer. @@ -2602,6 +2670,7 @@ This cannot be undone! Faster joining and more reliable messages. + Швидше приєднання та надійніші повідомлення. No comment provided by engineer. @@ -2656,10 +2725,12 @@ This cannot be undone! Finalize migration + Завершити міграцію No comment provided by engineer. Finalize migration on another device. + Завершіть міграцію на іншому пристрої. No comment provided by engineer. @@ -2709,6 +2780,7 @@ This cannot be undone! Found desktop + Знайдено робочий стіл No comment provided by engineer. @@ -2733,6 +2805,7 @@ This cannot be undone! Fully decentralized – visible only to members. + Повністю децентралізована - видима лише для учасників. No comment provided by engineer. @@ -2757,10 +2830,12 @@ This cannot be undone! Group already exists + Група вже існує No comment provided by engineer. Group already exists! + Група вже існує! No comment provided by engineer. @@ -2920,6 +2995,7 @@ This cannot be undone! History is not sent to new members. + Історія не надсилається новим учасникам. No comment provided by engineer. @@ -2949,6 +3025,7 @@ This cannot be undone! Hungarian interface + Інтерфейс угорською мовою No comment provided by engineer. @@ -3018,14 +3095,17 @@ This cannot be undone! Import failed + Не вдалося імпортувати No comment provided by engineer. Importing archive + Імпорт архіву No comment provided by engineer. Improved message delivery + Покращена доставка повідомлень No comment provided by engineer. @@ -3040,6 +3120,7 @@ This cannot be undone! In order to continue, chat should be stopped. + Для того, щоб продовжити, чат слід зупинити. No comment provided by engineer. @@ -3054,6 +3135,7 @@ This cannot be undone! Incognito groups + Групи інкогніто No comment provided by engineer. @@ -3088,6 +3170,7 @@ This cannot be undone! Incompatible version + Несумісна версія No comment provided by engineer. @@ -3134,6 +3217,7 @@ This cannot be undone! Invalid QR code + Неправильний QR-код No comment provided by engineer. @@ -3143,22 +3227,27 @@ This cannot be undone! Invalid display name! + Неправильне ім'я користувача! No comment provided by engineer. Invalid link + Невірне посилання No comment provided by engineer. Invalid migration confirmation + Недійсне підтвердження міграції No comment provided by engineer. Invalid name! + Неправильне ім'я! No comment provided by engineer. Invalid response + Неправильна відповідь No comment provided by engineer. @@ -3254,10 +3343,12 @@ This cannot be undone! Join group conversations + Приєднуйтесь до групових розмов No comment provided by engineer. Join group? + Приєднатися до групи? No comment provided by engineer. @@ -3267,11 +3358,14 @@ This cannot be undone! Join with current profile + Приєднатися з поточним профілем No comment provided by engineer. Join your group? This is your link for group %@! + Приєднатися до групи? +Це ваше посилання на групу %@! No comment provided by engineer. @@ -3281,14 +3375,17 @@ This is your link for group %@! Keep + Тримай No comment provided by engineer. Keep the app open to use it from desktop + Тримайте додаток відкритим, щоб використовувати його з робочого столу No comment provided by engineer. Keep unused invitation? + Зберігати невикористані запрошення? No comment provided by engineer. @@ -3353,14 +3450,17 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 + Зв'яжіть мобільні та десктопні додатки! 🔗 No comment provided by engineer. Linked desktop options + Параметри пов'язаного робочого столу No comment provided by engineer. Linked desktops + Пов'язані робочі столи No comment provided by engineer. @@ -3510,6 +3610,7 @@ This is your link for group %@! Message too large + Повідомлення занадто велике No comment provided by engineer. @@ -3524,38 +3625,47 @@ This is your link for group %@! Messages from %@ will be shown! + Повідомлення від %@ будуть показані! No comment provided by engineer. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Повідомлення, файли та дзвінки захищені **наскрізним шифруванням** з ідеальною секретністю переадресації, відмовою та відновленням після злому. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Повідомлення, файли та дзвінки захищені **квантово-стійким шифруванням e2e** з ідеальною секретністю переадресації, відмовою та відновленням після злому. No comment provided by engineer. Migrate device + Перенести пристрій No comment provided by engineer. Migrate from another device + Перехід з іншого пристрою No comment provided by engineer. Migrate here + Мігруйте сюди No comment provided by engineer. Migrate to another device + Перехід на інший пристрій No comment provided by engineer. Migrate to another device via QR code. + Перейдіть на інший пристрій за допомогою QR-коду. No comment provided by engineer. Migrating + Міграція No comment provided by engineer. @@ -3565,6 +3675,7 @@ This is your link for group %@! Migration complete + Міграція завершена No comment provided by engineer. @@ -3659,6 +3770,7 @@ This is your link for group %@! New chat + Новий чат No comment provided by engineer. @@ -3678,6 +3790,7 @@ This is your link for group %@! New desktop app! + Новий десктопний додаток! No comment provided by engineer. @@ -3762,6 +3875,7 @@ This is your link for group %@! Not compatible! + Не сумісні! No comment provided by engineer. @@ -3785,6 +3899,7 @@ This is your link for group %@! OK + ОК No comment provided by engineer. @@ -3899,6 +4014,7 @@ This is your link for group %@! Open + Відкрито No comment provided by engineer. @@ -3918,10 +4034,12 @@ This is your link for group %@! Open group + Відкрита група No comment provided by engineer. Open migration to another device + Відкрита міграція на інший пристрій authentication reason @@ -3936,22 +4054,27 @@ This is your link for group %@! Opening app… + Відкриваємо програму… No comment provided by engineer. Or paste archive link + Або вставте посилання на архів No comment provided by engineer. Or scan QR code + Або відскануйте QR-код No comment provided by engineer. Or securely share this file link + Або безпечно поділіться цим посиланням на файл No comment provided by engineer. Or show this code + Або покажіть цей код No comment provided by engineer. @@ -3996,10 +4119,12 @@ This is your link for group %@! Past member %@ + Колишній учасник %@ past/unknown group member Paste desktop address + Вставте адресу робочого столу No comment provided by engineer. @@ -4009,10 +4134,12 @@ This is your link for group %@! Paste link to connect! + Вставте посилання для підключення! No comment provided by engineer. Paste the link you received + Вставте отримане посилання No comment provided by engineer. @@ -4032,6 +4159,7 @@ This is your link for group %@! Picture-in-picture calls + Дзвінки "картинка в картинці No comment provided by engineer. @@ -4056,11 +4184,14 @@ This is your link for group %@! Please confirm that network settings are correct for this device. + Переконайтеся, що налаштування мережі для цього пристрою є правильними. No comment provided by engineer. Please contact developers. Error: %@ + Зверніться до розробників. +Помилка: %@ No comment provided by engineer. @@ -4115,6 +4246,7 @@ Error: %@ Post-quantum E2EE + Пост-квантовий E2EE No comment provided by engineer. @@ -4154,6 +4286,7 @@ Error: %@ Private notes + Приватні нотатки name of notes to self @@ -4168,10 +4301,12 @@ Error: %@ Profile name + Назва профілю No comment provided by engineer. Profile name: + Ім'я профілю: No comment provided by engineer. @@ -4251,10 +4386,12 @@ Error: %@ Push server + Push-сервер No comment provided by engineer. Quantum resistant encryption + Квантово-стійке шифрування No comment provided by engineer. @@ -4284,6 +4421,7 @@ Error: %@ Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. @@ -4343,6 +4481,7 @@ Error: %@ Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + Нещодавня історія та покращення [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). No comment provided by engineer. @@ -4437,22 +4576,27 @@ Error: %@ Repeat connection request? + Повторити запит на підключення? No comment provided by engineer. Repeat download + Повторити завантаження No comment provided by engineer. Repeat import + Повторний імпорт No comment provided by engineer. Repeat join request? + Повторити запит на приєднання? No comment provided by engineer. Repeat upload + Повторне завантаження No comment provided by engineer. @@ -4512,6 +4656,7 @@ Error: %@ Retry + Спробуйте ще раз No comment provided by engineer. @@ -4556,6 +4701,7 @@ Error: %@ Safer groups + Безпечніші групи No comment provided by engineer. @@ -4610,7 +4756,7 @@ Error: %@ Save preferences? - Зберегти налаштування? + Зберегти настройки? No comment provided by engineer. @@ -4645,6 +4791,7 @@ Error: %@ Saved message + Збережене повідомлення message info title @@ -4654,6 +4801,7 @@ Error: %@ Scan QR code from desktop + Відскануйте QR-код з робочого столу No comment provided by engineer. @@ -4678,10 +4826,12 @@ Error: %@ Search bar accepts invitation links. + Рядок пошуку приймає посилання-запрошення. No comment provided by engineer. Search or paste SimpleX link + Знайдіть або вставте посилання SimpleX No comment provided by engineer. @@ -4746,6 +4896,7 @@ Error: %@ Send direct message to connect + Надішліть пряме повідомлення, щоб підключитися No comment provided by engineer. @@ -4790,6 +4941,7 @@ Error: %@ Send up to 100 last messages to new members. + Надішліть до 100 останніх повідомлень новим користувачам. No comment provided by engineer. @@ -4889,6 +5041,7 @@ Error: %@ Session code + Код сесії No comment provided by engineer. @@ -4918,6 +5071,7 @@ Error: %@ Set passphrase + Встановити парольну фразу No comment provided by engineer. @@ -4967,6 +5121,7 @@ Error: %@ Share this 1-time invite link + Поділіться цим одноразовим посиланням-запрошенням No comment provided by engineer. @@ -4976,6 +5131,7 @@ Error: %@ Show QR code + Показати QR-код No comment provided by engineer. @@ -5065,6 +5221,7 @@ Error: %@ Simplified incognito mode + Спрощений режим інкогніто No comment provided by engineer. @@ -5099,6 +5256,7 @@ Error: %@ Start chat? + Почати чат? No comment provided by engineer. @@ -5118,6 +5276,7 @@ Error: %@ Stop chat + Припинити чат No comment provided by engineer. @@ -5162,6 +5321,7 @@ Error: %@ Stopping chat + Зупинка чату No comment provided by engineer. @@ -5216,6 +5376,7 @@ Error: %@ Tap to Connect + Натисніть, щоб підключитися No comment provided by engineer. @@ -5235,10 +5396,12 @@ Error: %@ Tap to paste link + Натисніть, щоб вставити посилання No comment provided by engineer. Tap to scan + Натисніть, щоб сканувати No comment provided by engineer. @@ -5305,6 +5468,7 @@ It can happen because of some bug or when the connection is compromised. The code you scanned is not a SimpleX link QR code. + Відсканований вами код не є QR-кодом посилання SimpleX. No comment provided by engineer. @@ -5374,6 +5538,7 @@ It can happen because of some bug or when the connection is compromised. The text you pasted is not a SimpleX link. + Текст, який ви вставили, не є посиланням SimpleX. No comment provided by engineer. @@ -5408,18 +5573,22 @@ It can happen because of some bug or when the connection is compromised. This chat is protected by end-to-end encryption. + Цей чат захищений наскрізним шифруванням. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. + Цей чат захищений квантово-стійким наскрізним шифруванням. E2EE info chat item This device name + Це ім'я пристрою No comment provided by engineer. This display name is invalid. Please choose another name. + Це ім'я для відображення є недійсним. Будь ласка, виберіть інше ім'я. No comment provided by engineer. @@ -5434,10 +5603,12 @@ It can happen because of some bug or when the connection is compromised. This is your own SimpleX address! + Це ваша власна SimpleX-адреса! No comment provided by engineer. This is your own one-time link! + Це ваше власне одноразове посилання! No comment provided by engineer. @@ -5457,6 +5628,7 @@ It can happen because of some bug or when the connection is compromised. To hide unwanted messages. + Приховати небажані повідомлення. No comment provided by engineer. @@ -5503,6 +5675,7 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle incognito when connecting. + Увімкніть інкогніто при підключенні. No comment provided by engineer. @@ -5522,6 +5695,7 @@ You will be prompted to complete authentication before this feature is enabled.< Turkish interface + Турецький інтерфейс No comment provided by engineer. @@ -5541,22 +5715,27 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock + Розблoкувати No comment provided by engineer. Unblock for all + Розблокування для всіх No comment provided by engineer. Unblock member + Розблокувати учасника No comment provided by engineer. Unblock member for all? + Розблокувати учасника для всіх? No comment provided by engineer. Unblock member? + Розблокувати учасника? No comment provided by engineer. @@ -5623,10 +5802,12 @@ To connect, please ask your contact to create another connection link and check Unlink + Роз'єднати зв'язок No comment provided by engineer. Unlink desktop? + Від'єднати робочий стіл? No comment provided by engineer. @@ -5651,6 +5832,7 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. + Новим користувачам надсилається до 100 останніх повідомлень. No comment provided by engineer. @@ -5695,6 +5877,7 @@ To connect, please ask your contact to create another connection link and check Upload failed + Не вдалося завантфжити No comment provided by engineer. @@ -5704,6 +5887,7 @@ To connect, please ask your contact to create another connection link and check Uploading archive + Завантаження архіву No comment provided by engineer. @@ -5733,6 +5917,7 @@ To connect, please ask your contact to create another connection link and check Use from desktop + Використання з робочого столу No comment provided by engineer. @@ -5747,6 +5932,7 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? + Використовувати лише локальні сповіщення? No comment provided by engineer. @@ -5756,6 +5942,7 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. + Використовуйте додаток під час розмови. No comment provided by engineer. @@ -5775,10 +5962,12 @@ To connect, please ask your contact to create another connection link and check Verify code with desktop + Перевірте код на робочому столі No comment provided by engineer. Verify connection + Перевірте з'єднання No comment provided by engineer. @@ -5788,14 +5977,17 @@ To connect, please ask your contact to create another connection link and check Verify connections + Пeревірте з'єднання No comment provided by engineer. Verify database passphrase + Перевірте пароль до бази даних No comment provided by engineer. Verify passphrase + Підтвердіть парольну фразу No comment provided by engineer. @@ -5810,6 +6002,7 @@ To connect, please ask your contact to create another connection link and check Via secure quantum resistant protocol. + Через безпечний квантово-стійкий протокол. No comment provided by engineer. @@ -5839,6 +6032,7 @@ To connect, please ask your contact to create another connection link and check Visible history + Видима історія chat feature @@ -5868,6 +6062,7 @@ To connect, please ask your contact to create another connection link and check Waiting for desktop... + Чекаємо на десктопну версію... No comment provided by engineer. @@ -5887,6 +6082,7 @@ To connect, please ask your contact to create another connection link and check Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Попередження: запуск чату на декількох пристроях не підтримується і може призвести до збоїв у доставці повідомлень No comment provided by engineer. @@ -5911,6 +6107,7 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long + Привітальне повідомлення занадто довге No comment provided by engineer. @@ -5935,6 +6132,7 @@ To connect, please ask your contact to create another connection link and check With encrypted files and media. + З зашифрованими файлами та медіа. No comment provided by engineer. @@ -5944,6 +6142,7 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. + З меншим споживанням заряду акумулятора. No comment provided by engineer. @@ -5968,6 +6167,7 @@ To connect, please ask your contact to create another connection link and check You **must not** use the same database on two devices. + Ви **не повинні використовувати** одну і ту ж базу даних на двох пристроях. No comment provided by engineer. @@ -5992,31 +6192,39 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. + Ви вже з'єднані з %@. No comment provided by engineer. You are already connecting via this one-time link! + Ви вже підключаєтеся до %@.Ви вже підключаєтеся за цим одноразовим посиланням! No comment provided by engineer. You are already in group %@. + Ви вже перебуваєте в групі %@. No comment provided by engineer. You are already joining the group %@. + Ви вже приєдналися до групи %@. No comment provided by engineer. You are already joining the group via this link! + Ви вже приєдналися до групи за цим посиланням! No comment provided by engineer. You are already joining the group via this link. + Ви вже приєдналися до групи за цим посиланням. No comment provided by engineer. You are already joining the group! Repeat join request? + Ви вже приєдналися до групи! +Повторити запит на приєднання? No comment provided by engineer. @@ -6051,6 +6259,7 @@ Repeat join request? You can give another try. + Ви можете спробувати ще раз. No comment provided by engineer. @@ -6060,6 +6269,7 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. + Ви можете зробити його видимим для ваших контактів у SimpleX за допомогою налаштувань. No comment provided by engineer. @@ -6104,6 +6314,7 @@ Repeat join request? You can view invitation link again in connection details. + Ви можете переглянути посилання на запрошення ще раз у деталях підключення. No comment provided by engineer. @@ -6123,11 +6334,14 @@ Repeat join request? You have already requested connection via this address! + Ви вже надсилали запит на підключення за цією адресою! No comment provided by engineer. You have already requested connection! Repeat connection request? + Ви вже надіслали запит на підключення! +Повторити запит на підключення? No comment provided by engineer. @@ -6182,6 +6396,7 @@ Repeat connection request? You will be connected when group link host's device is online, please wait or check later! + Ви будете підключені, коли пристрій хоста групового посилання буде онлайн, будь ласка, зачекайте або перевірте пізніше! No comment provided by engineer. @@ -6201,6 +6416,7 @@ Repeat connection request? You will connect to all group members. + Ви з'єднаєтеся з усіма учасниками групи. No comment provided by engineer. @@ -6317,6 +6533,7 @@ You can cancel this connection and remove the contact (and try later with a new Your profile + Ваш профіль No comment provided by engineer. @@ -6413,6 +6630,7 @@ SimpleX servers cannot see your profile. and %lld other events + та %lld інших подій No comment provided by engineer. @@ -6422,6 +6640,7 @@ SimpleX servers cannot see your profile. author + автор member role @@ -6436,14 +6655,17 @@ SimpleX servers cannot see your profile. blocked + заблоковано marked deleted chat item preview text blocked %@ + заблоковано %@ rcv group event chat item blocked by admin + заблоковано адміністратором marked deleted chat item preview text @@ -6518,6 +6740,7 @@ SimpleX servers cannot see your profile. connected directly + з'єднані безпосередньо rcv group event chat item @@ -6567,6 +6790,7 @@ SimpleX servers cannot see your profile. contact %1$@ changed to %2$@ + контакт %1$@ змінено на %2$@ profile update event chat item @@ -6621,6 +6845,7 @@ SimpleX servers cannot see your profile. deleted contact + видалений контакт rcv direct event chat item @@ -6840,6 +7065,7 @@ SimpleX servers cannot see your profile. member %1$@ changed to %2$@ + учасника %1$@ змінено на %2$@ profile update event chat item @@ -6926,7 +7152,7 @@ SimpleX servers cannot see your profile. on - увімкнено + увімкненo group pref value @@ -6941,6 +7167,7 @@ SimpleX servers cannot see your profile. quantum resistant e2e encryption + квантово-стійке шифрування e2e chat item text @@ -6970,10 +7197,12 @@ SimpleX servers cannot see your profile. removed contact address + видалено контактну адресу profile update event chat item removed profile picture + видалено зображення профілю profile update event chat item @@ -7003,18 +7232,22 @@ SimpleX servers cannot see your profile. send direct message + надіслати пряме повідомлення No comment provided by engineer. set new contact address + встановити нову контактну адресу profile update event chat item set new profile picture + встановити нове зображення профілю profile update event chat item standard end-to-end encryption + стандартне наскрізне шифрування chat item text @@ -7034,6 +7267,7 @@ SimpleX servers cannot see your profile. unblocked %@ + розблоковано %@ rcv group event chat item @@ -7043,6 +7277,7 @@ SimpleX servers cannot see your profile. unknown status + невідомий статус No comment provided by engineer. @@ -7052,10 +7287,12 @@ SimpleX servers cannot see your profile. updated profile + оновлений профіль profile update event chat item v%@ + v%@ No comment provided by engineer. @@ -7125,6 +7362,7 @@ SimpleX servers cannot see your profile. you blocked %@ + ви заблокували %@ snd group event chat item @@ -7169,6 +7407,7 @@ SimpleX servers cannot see your profile. you unblocked %@ + ви розблокували %@ snd group event chat item @@ -7205,6 +7444,7 @@ SimpleX servers cannot see your profile. SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + SimpleX використовує доступ до локальної мережі, щоб дозволити користувачеві користуватися профілем чату через десктопну програму в тій же мережі. Privacy - Local Network Usage Description diff --git a/apps/ios/SimpleX NSE/ConcurrentQueue.swift b/apps/ios/SimpleX NSE/ConcurrentQueue.swift deleted file mode 100644 index 274a683c00..0000000000 --- a/apps/ios/SimpleX NSE/ConcurrentQueue.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// ConcurrentQueue.swift -// SimpleX NSE -// -// Created by Evgeny on 08/12/2023. -// Copyright © 2023 SimpleX Chat. All rights reserved. -// - -import Foundation - -struct DequeueElement { - var elementId: UUID? - var task: Task -} - -class ConcurrentQueue { - private var queue: [T] = [] - private var queueLock = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.concurrent-queue.lock.\(UUID())") - private var continuations = [(elementId: UUID, continuation: CheckedContinuation)]() - - func enqueue(_ el: T) { - resumeContinuation(el) { self.queue.append(el) } - } - - func frontEnqueue(_ el: T) { - resumeContinuation(el) { self.queue.insert(el, at: 0) } - } - - private func resumeContinuation(_ el: T, add: @escaping () -> Void) { - queueLock.sync { - if let (_, cont) = continuations.first { - continuations.remove(at: 0) - cont.resume(returning: el) - } else { - add() - } - } - } - - func dequeue() -> DequeueElement { - queueLock.sync { - if queue.isEmpty { - let elementId = UUID() - let task = Task { - await withCheckedContinuation { cont in - continuations.append((elementId, cont)) - } - } - return DequeueElement(elementId: elementId, task: task) - } else { - let el = queue.remove(at: 0) - return DequeueElement(task: Task { el }) - } - } - } - - func cancelDequeue(_ elementId: UUID) { - queueLock.sync { - let cancelled = continuations.filter { $0.elementId == elementId } - continuations.removeAll { $0.elementId == elementId } - cancelled.forEach { $0.continuation.resume(returning: nil) } - } - } -} diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 6f76781837..efd8f33dd8 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -22,110 +22,6 @@ let nseSuspendSchedule: SuspendSchedule = (2, 4) let fastNSESuspendSchedule: SuspendSchedule = (1, 1) -typealias NtfStream = ConcurrentQueue - -// Notifications are delivered via concurrent queues, as they are all received from chat controller in a single loop that -// writes to ConcurrentQueue and when notification is processed, the instance of Notification service extension reads from the queue. -// One queue per connection (entity) is used. -// The concurrent queues allow read cancellation, to ensure that notifications are not lost in case the current thread completes -// before expected notification is read (multiple notifications can be expected, because one notification can be delivered for several messages). -actor PendingNtfs { - static let shared = PendingNtfs() - private var ntfStreams: [String: NtfStream] = [:] - - func createStream(_ id: String) async { - logger.debug("NotificationService PendingNtfs.createStream: \(id)") - if ntfStreams[id] == nil { - ntfStreams[id] = ConcurrentQueue() - logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue") - } - } - - func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async { - logger.debug("NotificationService PendingNtfs.readStream: \(id) \(ntfInfo.ntfMessages.count)") - if !ntfInfo.user.showNotifications { - nse.setBestAttemptNtf(.empty) - } - if let s = ntfStreams[id] { - logger.debug("NotificationService PendingNtfs.readStream: has stream") - var expected = Set(ntfInfo.ntfMessages.map { $0.msgId }) - logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected)") - var readCancelled = false - var dequeued: DequeueElement? - nse.cancelRead = { - readCancelled = true - if let elementId = dequeued?.elementId { - s.cancelDequeue(elementId) - } - } - while !readCancelled { - dequeued = s.dequeue() - if let ntf = await dequeued?.task.value { - if readCancelled { - logger.debug("NotificationService PendingNtfs.readStream: read cancelled, put ntf to queue front") - s.frontEnqueue(ntf) - break - } else if case let .msgInfo(info) = ntf { - let found = expected.remove(info.msgId) - if found != nil { - logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty)") - if expected.isEmpty { break } - } else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs { - logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo") - s.frontEnqueue(ntf) - break - } - } else if ntfInfo.user.showNotifications { - logger.debug("NotificationService PendingNtfs.readStream: setting best attempt") - nse.setBestAttemptNtf(ntf) - if ntf.isCallInvitation { break } - } - } else { - break - } - } - nse.cancelRead = nil - logger.debug("NotificationService PendingNtfs.readStream: exiting") - } - } - - func writeStream(_ id: String, _ ntf: NSENotification) async { - logger.debug("NotificationService PendingNtfs.writeStream: \(id)") - if let s = ntfStreams[id] { - logger.debug("NotificationService PendingNtfs.writeStream: writing ntf") - s.enqueue(ntf) - } - } -} - -// The current implementation assumes concurrent notification delivery and uses semaphores -// to process only one notification per connection (entity) at a time. -class NtfStreamSemaphores { - static let shared = NtfStreamSemaphores() - private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-semaphores.lock") - private var semaphores: [String: DispatchSemaphore] = [:] - - func waitForStream(_ id: String) { - streamSemaphore(id, value: 0)?.wait() - } - - func signalStreamReady(_ id: String) { - streamSemaphore(id, value: 1)?.signal() - } - - // this function returns nil if semaphore is just created, so passed value shoud be coordinated with the desired end value of the semaphore - private func streamSemaphore(_ id: String, value: Int) -> DispatchSemaphore? { - NtfStreamSemaphores.queue.sync { - if let s = semaphores[id] { - return s - } else { - semaphores[id] = DispatchSemaphore(value: value) - return nil - } - } - } -} - enum NSENotification { case nse(UNMutableNotificationContent) case callkit(RcvCallInvitation) @@ -149,7 +45,7 @@ class NSEThreads { static let shared = NSEThreads() private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") private var allThreads: Set = [] - private var activeThreads: Set = [] + private var activeThreads: [(UUID, NotificationService)] = [] func newThread() -> UUID { NSEThreads.queue.sync { @@ -158,19 +54,42 @@ class NSEThreads { } } - func startThread(_ t: UUID) { + func startThread(_ t: UUID, _ service: NotificationService) { NSEThreads.queue.sync { if allThreads.contains(t) { - _ = activeThreads.insert(t) + activeThreads.append((t, service)) } else { logger.warning("NotificationService startThread: thread \(t) was removed before it started") } } } + func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void { + var waitTime: Int64 = 5_000_000000 + while waitTime > 0 { + if let (_, nse) = rcvEntityThread(id), + nse.shouldProcessNtf && nse.processReceivedNtf(ntf) { + break + } else { + try? await Task.sleep(nanoseconds: 10_000000) + waitTime -= 10_000000 + } + } + } + + private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? { + NSEThreads.queue.sync { + activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id }) + } + } + func endThread(_ t: UUID) -> Bool { NSEThreads.queue.sync { - let tActive = activeThreads.remove(t) + let tActive: UUID? = if let index = activeThreads.firstIndex(where: { $0.0 == t }) { + activeThreads.remove(at: index).0 + } else { + nil + } let t = allThreads.remove(t) if tActive != nil && activeThreads.isEmpty { return true @@ -198,8 +117,11 @@ class NotificationService: UNNotificationServiceExtension { // thread is added to allThreads here - if thread did not start chat, // chat does not need to be suspended but NSE state still needs to be set to "suspended". var threadId: UUID? = NSEThreads.shared.newThread() + var notificationInfo: NtfMessages? var receiveEntityId: String? - var cancelRead: (() -> Void)? + var expectedMessages: Set = [] + // return true if the message is taken - it prevents sending it to another NotificationService instance for processing + var shouldProcessNtf = false var appSubscriber: AppSubscriber? var returnedSuspension = false @@ -265,7 +187,7 @@ class NotificationService: UNNotificationServiceExtension { // check it here again appStateGroupDefault.get().inactive { // thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended - if let t = threadId { NSEThreads.shared.startThread(t) } + if let t = threadId { NSEThreads.shared.startThread(t, self) } let dbStatus = startChat() if case .ok = dbStatus, let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { @@ -276,17 +198,11 @@ class NotificationService: UNNotificationServiceExtension { ? .nse(createConnectionEventNtf(ntfInfo.user, connEntity)) : .empty ) - if let id = connEntity.id { + if let id = connEntity.id, ntfInfo.msgTs != nil { + notificationInfo = ntfInfo receiveEntityId = id - NtfStreamSemaphores.shared.waitForStream(id) - if receiveEntityId != nil { - Task { - logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id)") - await PendingNtfs.shared.createStream(id) - await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo) - deliverBestAttemptNtf() - } - } + expectedMessages = Set(ntfInfo.ntfMessages.map { $0.msgId }) + shouldProcessNtf = true return } } @@ -302,6 +218,38 @@ class NotificationService: UNNotificationServiceExtension { deliverBestAttemptNtf(urgent: true) } + func processReceivedNtf(_ ntf: NSENotification) -> Bool { + guard let ntfInfo = notificationInfo, let msgTs = ntfInfo.msgTs else { return false } + if !ntfInfo.user.showNotifications { + self.setBestAttemptNtf(.empty) + } + if case let .msgInfo(info) = ntf { + let found = expectedMessages.remove(info.msgId) + if found != nil { + logger.debug("NotificationService processNtf: msgInfo, last: \(self.expectedMessages.isEmpty)") + if expectedMessages.isEmpty { + self.deliverBestAttemptNtf() + } + return true + } else if info.msgTs > msgTs { + logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one") + self.deliverBestAttemptNtf() + return false + } else { + logger.debug("NotificationService processNtf: unknown message, let other instance to process it") + return false + } + } else if ntfInfo.user.showNotifications { + logger.debug("NotificationService processNtf: setting best attempt") + self.setBestAttemptNtf(ntf) + if ntf.isCallInvitation { + self.deliverBestAttemptNtf() + } + return true + } + return false + } + func setBadgeCount() { badgeCount = ntfBadgeCountGroupDefault.get() + 1 ntfBadgeCountGroupDefault.set(badgeCount) @@ -323,14 +271,9 @@ class NotificationService: UNNotificationServiceExtension { private func deliverBestAttemptNtf(urgent: Bool = false) { logger.debug("NotificationService.deliverBestAttemptNtf") - if let cancel = cancelRead { - cancelRead = nil - cancel() - } - if let id = receiveEntityId { - receiveEntityId = nil - NtfStreamSemaphores.shared.signalStreamReady(id) - } + // stop processing other messages + shouldProcessNtf = false + let suspend: Bool if let t = threadId { threadId = nil @@ -572,7 +515,7 @@ func chatSuspended() { } // A single loop is used per Notification service extension process to receive and process all messages depending on the NSE state -// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will no be received. +// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will not be received. func receiveMessages() async { logger.debug("NotificationService receiveMessages") while true { @@ -591,8 +534,7 @@ func receiveMessages() async { logger.debug("NotificationService receiveMsg: message") if let (id, ntf) = await receivedMsgNtf(msg) { logger.debug("NotificationService receiveMsg: notification") - await PendingNtfs.shared.createStream(id) - await PendingNtfs.shared.writeStream(id, ntf) + await NSEThreads.shared.processNotification(id, ntf) } } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3e27aeec5a..96f95dd7fe 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -36,11 +36,6 @@ 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; }; 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; }; - 5C371E6A2BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E652BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a */; }; - 5C371E6B2BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E662BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a */; }; - 5C371E6C2BA9FDFA00100AD3 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E672BA9FDFA00100AD3 /* libgmpxx.a */; }; - 5C371E6D2BA9FDFA00100AD3 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E682BA9FDFA00100AD3 /* libffi.a */; }; - 5C371E6E2BA9FDFA00100AD3 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E692BA9FDFA00100AD3 /* libgmp.a */; }; 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; }; 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; }; 5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */; }; @@ -144,7 +139,11 @@ 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; }; 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; }; 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; }; - 5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */; }; + 5CF898622BB984E400EE33B6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985D2BB984E400EE33B6 /* libgmpxx.a */; }; + 5CF898632BB984E400EE33B6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985E2BB984E400EE33B6 /* libffi.a */; }; + 5CF898642BB984E400EE33B6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985F2BB984E400EE33B6 /* libgmp.a */; }; + 5CF898652BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */; }; + 5CF898662BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */; }; 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; }; @@ -296,11 +295,6 @@ 5C371E4E2BA9AAA200100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; 5C371E4F2BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = "hu.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5C371E502BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; - 5C371E652BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a"; sourceTree = ""; }; - 5C371E662BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a"; sourceTree = ""; }; - 5C371E672BA9FDFA00100AD3 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C371E682BA9FDFA00100AD3 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C371E692BA9FDFA00100AD3 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = ""; }; 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = ""; }; 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectDesktopView.swift; sourceTree = ""; }; @@ -442,7 +436,11 @@ 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = ""; }; 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = ""; }; 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = ""; }; - 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentQueue.swift; sourceTree = ""; }; + 5CF8985D2BB984E400EE33B6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5CF8985E2BB984E400EE33B6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5CF8985F2BB984E400EE33B6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a"; sourceTree = ""; }; + 5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a"; sourceTree = ""; }; 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; }; 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = ""; }; @@ -533,13 +531,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C371E6A2BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a in Frameworks */, - 5C371E6E2BA9FDFA00100AD3 /* libgmp.a in Frameworks */, - 5C371E6D2BA9FDFA00100AD3 /* libffi.a in Frameworks */, - 5C371E6C2BA9FDFA00100AD3 /* libgmpxx.a in Frameworks */, - 5C371E6B2BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a in Frameworks */, + 5CF898652BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */, + 5CF898622BB984E400EE33B6 /* libgmpxx.a in Frameworks */, + 5CF898642BB984E400EE33B6 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 5CF898632BB984E400EE33B6 /* libffi.a in Frameworks */, + 5CF898662BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -602,11 +600,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C371E682BA9FDFA00100AD3 /* libffi.a */, - 5C371E692BA9FDFA00100AD3 /* libgmp.a */, - 5C371E672BA9FDFA00100AD3 /* libgmpxx.a */, - 5C371E662BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH-ghc9.6.3.a */, - 5C371E652BA9FDFA00100AD3 /* libHSsimplex-chat-5.6.0.3-4nskXXUBgQ3Bvo5v4RfruH.a */, + 5CF8985E2BB984E400EE33B6 /* libffi.a */, + 5CF8985F2BB984E400EE33B6 /* libgmp.a */, + 5CF8985D2BB984E400EE33B6 /* libgmpxx.a */, + 5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */, + 5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */, ); path = Libraries; sourceTree = ""; @@ -810,7 +808,6 @@ isa = PBXGroup; children = ( 5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */, - 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */, 5CDCAD472818589900503DA2 /* NotificationService.swift */, 5CDCAD492818589900503DA2 /* Info.plist */, 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */, @@ -1295,7 +1292,6 @@ files = ( 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */, 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */, - 5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1455,6 +1451,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1516,6 +1513,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1544,12 +1542,16 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; + GCC_OPTIMIZATION_LEVEL = s; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SimpleX--iOS--Info.plist"; INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls."; @@ -1568,11 +1570,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + LLVM_LTO = YES_THIN; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -1587,12 +1591,16 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; - ENABLE_PREVIEWS = YES; + ENABLE_CODE_COVERAGE = NO; + ENABLE_PREVIEWS = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SimpleX--iOS--Info.plist"; INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls."; @@ -1611,6 +1619,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + LLVM_LTO = YES; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -1667,12 +1676,15 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; + GCC_OPTIMIZATION_LEVEL = s; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SimpleX NSE/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE"; @@ -1683,6 +1695,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + LLVM_LTO = YES; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1690,6 +1703,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Osize"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -1699,12 +1713,15 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; + ENABLE_CODE_COVERAGE = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SimpleX NSE/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE"; @@ -1715,6 +1732,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + LLVM_LTO = YES; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1722,6 +1740,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Osize"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; VALIDATE_PRODUCT = YES; @@ -1733,14 +1752,17 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_BITCODE = NO; + GCC_OPTIMIZATION_LEVEL = s; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1758,6 +1780,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); + LLVM_LTO = YES; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1767,6 +1790,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1779,14 +1803,17 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; + CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 203; + CURRENT_PROJECT_VERSION = 204; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_BITCODE = NO; + ENABLE_CODE_COVERAGE = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1804,6 +1831,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); + LLVM_LTO = YES; MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1813,6 +1841,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f55c69a349..b18022cfec 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -557,11 +557,6 @@ public enum ChatResponse: Decodable, Error { case contactRequestRejected(user: UserRef) case contactUpdated(user: UserRef, toContact: Contact) case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember) - // TODO remove events below - case contactsSubscribed(server: String, contactRefs: [ContactRef]) - case contactsDisconnected(server: String, contactRefs: [ContactRef]) - case contactSubSummary(user: UserRef, contactSubscriptions: [ContactSubStatus]) - // TODO remove events above case networkStatus(networkStatus: NetworkStatus, connections: [String]) case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) case groupSubscribed(user: UserRef, groupInfo: GroupRef) @@ -724,9 +719,6 @@ public enum ChatResponse: Decodable, Error { case .contactRequestRejected: return "contactRequestRejected" case .contactUpdated: return "contactUpdated" case .groupMemberUpdated: return "groupMemberUpdated" - case .contactsSubscribed: return "contactsSubscribed" - case .contactsDisconnected: return "contactsDisconnected" - case .contactSubSummary: return "contactSubSummary" case .networkStatus: return "networkStatus" case .networkStatuses: return "networkStatuses" case .groupSubscribed: return "groupSubscribed" @@ -885,9 +877,6 @@ public enum ChatResponse: Decodable, Error { case .contactRequestRejected: return noDetails case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact)) case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)") - case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))" - case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))" - case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions)) case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo)) @@ -1827,6 +1816,7 @@ public enum AgentErrorType: Decodable { case BROKER(brokerAddress: String, brokerErr: BrokerErrorType) case AGENT(agentErr: SMPAgentError) case INTERNAL(internalErr: String) + case CRITICAL(offerRestart: Bool, criticalErr: String) case INACTIVE } @@ -1878,6 +1868,8 @@ public enum XFTPErrorType: Decodable { case NO_FILE case HAS_FILE case FILE_IO + case TIMEOUT + case REDIRECT(redirectError: String) case INTERNAL } @@ -1885,6 +1877,8 @@ public enum RCErrorType: Decodable { case `internal`(internalErr: String) case identity case noLocalAddress + case newController + case notDiscovered case tlsStartFailed case exception(exception: String) case ctrlAuth @@ -1910,6 +1904,7 @@ public enum ProtocolTransportError: Decodable { case badBlock case largeMsg case badSession + case noServerAuth case handshake(handshakeErr: SMPHandshakeError) } @@ -1917,6 +1912,7 @@ public enum SMPHandshakeError: Decodable { case PARSE case VERSION case IDENTITY + case BAD_AUTH } public enum SMPAgentError: Decodable { @@ -1938,10 +1934,13 @@ public enum RemoteCtrlError: Decodable { case badState case busy case timeout + case noKnownControllers + case badController case disconnected(remoteCtrlId: Int64, reason: String) case badInvitation case badVersion(appVersion: String) -// case protocolError(protocolError: RemoteProtocolError) + case hTTP2Error(http2Error: String) + case protocolError } public struct MigrationFileLinkData: Codable { diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 4fbe78dc7a..df4de134c2 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -197,7 +197,7 @@ public let confirmDBUpgradesGroupDefault = BoolDefault(defaults: groupDefaults, public let callKitEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CALL_KIT_ENABLED) -public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES) +public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED) public class DateDefault { var defaults: UserDefaults diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index b74a2517c7..5e1c0ac538 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2120,7 +2120,7 @@ public enum ConnectionEntity: Decodable { public var id: String? { switch self { case let .rcvDirectMsgConnection(contact): - return contact?.id ?? nil + return contact?.id case let .rcvGroupMsgConnection(_, groupMember): return groupMember.id case let .userContactConnection(userContact): diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 580c20d65a..44b4c6de9f 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -377,6 +377,9 @@ /* member role */ "admin" = "админ"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Администраторите могат да блокират член за всички."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Админите могат да създадат линкове за присъединяване към групи."; @@ -416,6 +419,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Всички ваши контакти ще останат свързани. Актуализацията на профила ще бъде изпратена до вашите контакти."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Всички ваши контакти, разговори и файлове ще бъдат сигурно криптирани и качени на парчета в конфигурираните XFTP релета."; + /* No comment provided by engineer. */ "Allow" = "Позволи"; @@ -497,6 +503,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Компилация на приложението: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Миграция на данните от приложението"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Приложението криптира нови локални файлове (с изключение на видеоклипове)."; @@ -518,6 +527,15 @@ /* No comment provided by engineer. */ "Appearance" = "Изглед"; +/* No comment provided by engineer. */ +"Apply" = "Приложи"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Архивиране и качване"; + +/* No comment provided by engineer. */ +"Archiving database" = "Архивиране на база данни"; + /* No comment provided by engineer. */ "Attach" = "Прикачи"; @@ -665,6 +683,9 @@ /* No comment provided by engineer. */ "Cancel" = "Отказ"; +/* No comment provided by engineer. */ +"Cancel migration" = "Отмени миграцията"; + /* feature offered item */ "cancelled %@" = "отменен %@"; @@ -744,6 +765,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Чатът е мигриран!"; + /* No comment provided by engineer. */ "Chat preferences" = "Чат настройки"; @@ -801,6 +825,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Потвърди актуализаациите на базата данни"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Потвърди мрежовите настройки"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Потвърди новата парола…"; @@ -810,6 +837,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Потвърди парола"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Потвърдете, че помните паролата на базата данни, преди да я мигрирате."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Потвърди качването"; + /* server test step */ "Connect" = "Свързване"; @@ -1008,6 +1041,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Създаден на %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Създаване на архивен линк"; + /* No comment provided by engineer. */ "Creating link…" = "Линкът се създава…"; @@ -1155,6 +1191,9 @@ /* No comment provided by engineer. */ "Delete database" = "Изтрий базата данни"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Изтриване на базата данни от това устройство"; + /* server test step */ "Delete file" = "Изтрий файл"; @@ -1347,9 +1386,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Понижи версията и отвори чата"; +/* No comment provided by engineer. */ +"Download failed" = "Неуспешно изтегляне"; + /* server test step */ "Download file" = "Свали файл"; +/* No comment provided by engineer. */ +"Downloading archive" = "Архива се изтегля"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Подробности за линка се изтеглят"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Дублирано име!"; @@ -1383,6 +1431,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Активиране за всички"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Активиране в личните чатове (БЕТА)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Активирай незабавни известия?"; @@ -1497,6 +1548,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Въведете kодa за достъп"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Въведи парола"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Въведи парола…"; @@ -1536,6 +1590,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Грешка при добавяне на член(ове)"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Грешка при разрешаване на PQ криптиране за контакт"; + /* No comment provided by engineer. */ "Error changing address" = "Грешка при промяна на адреса"; @@ -1590,6 +1647,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Грешка при изтриване на потребителския профил"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Грешка при изтеглянето на архива"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Грешка при активирането на потвърждениeто за доставка!"; @@ -1635,6 +1695,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Грешка при запазване на парола в Кeychain"; +/* when migrating */ +"Error saving settings" = "Грешка при запазване на настройките"; + /* No comment provided by engineer. */ "Error saving user password" = "Грешка при запазване на потребителска парола"; @@ -1677,6 +1740,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Грешка при актуализиране на поверителността на потребителя"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Грешка при качването на архива"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Грешка при проверката на паролата:"; + /* No comment provided by engineer. */ "Error: " = "Грешка: "; @@ -1710,6 +1779,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Експортиран архив на базата данни."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Експортираният файл не съществува"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Експортиране на архив на базата данни…"; @@ -1752,6 +1824,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Филтрирайте непрочетените и любимите чатове."; +/* No comment provided by engineer. */ +"Finalize migration" = "Завърши миграцията"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Завършете миграцията на другото устройство."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Най-накрая ги имаме! 🚀"; @@ -1935,6 +2013,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Как да използвате вашите сървъри"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Унгарски интерфейс"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE сървъри (по един на ред)"; @@ -1974,6 +2055,12 @@ /* No comment provided by engineer. */ "Import database" = "Импортиране на база данни"; +/* No comment provided by engineer. */ +"Import failed" = "Неуспешно импортиране"; + +/* No comment provided by engineer. */ +"Importing archive" = "Импортиране на архив"; + /* No comment provided by engineer. */ "Improved message delivery" = "Подобрена доставка на съобщения"; @@ -1983,6 +2070,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Подобрена конфигурация на сървъра"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "За да продължите, чатът трябва да бъде спрян."; + /* No comment provided by engineer. */ "In reply to" = "В отговор на"; @@ -2067,6 +2157,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Невалиден линк"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Невалидно потвърждение за мигриране"; + /* No comment provided by engineer. */ "Invalid name!" = "Невалидно име!"; @@ -2331,6 +2424,9 @@ /* No comment provided by engineer. */ "Message text" = "Текст на съобщението"; +/* No comment provided by engineer. */ +"Message too large" = "Съобщението е твърде голямо"; + /* No comment provided by engineer. */ "Messages" = "Съобщения"; @@ -2340,9 +2436,30 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Съобщенията от %@ ще бъдат показани!"; +/* No comment provided by engineer. */ +"Migrate device" = "Мигрирай устройството"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Мигриране от друго устройство"; + +/* No comment provided by engineer. */ +"Migrate here" = "Мигрирай тук"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Миграция към друго устройство"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Мигрирайте към друго устройство чрез QR код."; + +/* No comment provided by engineer. */ +"Migrating" = "Мигриране"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Архивът на базата данни се мигрира…"; +/* No comment provided by engineer. */ +"Migration complete" = "Миграцията е завършена"; + /* No comment provided by engineer. */ "Migration error:" = "Грешка при мигриране:"; @@ -2600,6 +2717,9 @@ /* No comment provided by engineer. */ "Open group" = "Отвори група"; +/* authentication reason */ +"Open migration to another device" = "Отвори миграцията към друго устройство"; + /* No comment provided by engineer. */ "Open Settings" = "Отвори настройки"; @@ -2612,9 +2732,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Приложението се отваря…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Или постави архивен линк"; + /* No comment provided by engineer. */ "Or scan QR code" = "Или сканирай QR код"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Или сигурно споделете този линк към файла"; + /* No comment provided by engineer. */ "Or show this code" = "Или покажи този код"; @@ -2666,6 +2792,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Постоянна грешка при декриптиране"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Обаждания \"картина в картина\""; + /* No comment provided by engineer. */ "PING count" = "PING бройка"; @@ -2684,6 +2813,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Моля, проверете вашите настройки и тези вашия за контакт."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Моля, потвърдете, че мрежовите настройки са правилни за това устройство."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Моля, свържете се с разработчиците.\nГрешка: %@"; @@ -2717,6 +2849,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Постквантово E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове."; @@ -2798,6 +2933,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Push известия"; +/* No comment provided by engineer. */ +"Push server" = "Push сървър"; + +/* chat item text */ +"quantum resistant e2e encryption" = "квантово устойчиво e2e криптиране"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Квантово устойчиво криптиране"; + /* No comment provided by engineer. */ "Rate the app" = "Оценете приложението"; @@ -2933,9 +3077,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Изпрати отново заявката за свързване?"; +/* No comment provided by engineer. */ +"Repeat download" = "Повтори изтеглянето"; + +/* No comment provided by engineer. */ +"Repeat import" = "Повтори импортирането"; + /* No comment provided by engineer. */ "Repeat join request?" = "Изпрати отново заявката за присъединяване?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Повтори качването"; + /* chat item action */ "Reply" = "Отговори"; @@ -2993,6 +3146,9 @@ /* No comment provided by engineer. */ "Run chat" = "Стартиране на чат"; +/* No comment provided by engineer. */ +"Safer groups" = "По-безопасни групи"; + /* chat item action */ "Save" = "Запази"; @@ -3233,6 +3389,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Задай kод за достъп"; +/* No comment provided by engineer. */ +"Set passphrase" = "Задаване на парола"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Задай парола за експортиране"; @@ -3278,6 +3437,9 @@ /* No comment provided by engineer. */ "Show preview" = "Показване на визуализация"; +/* No comment provided by engineer. */ +"Show QR code" = "Покажи QR код"; + /* No comment provided by engineer. */ "Show:" = "Покажи:"; @@ -3338,6 +3500,9 @@ /* notification title */ "Somebody" = "Някой"; +/* chat item text */ +"standard end-to-end encryption" = "стандартно криптиране от край до край"; + /* No comment provided by engineer. */ "Start chat" = "Започни чат"; @@ -3353,6 +3518,9 @@ /* No comment provided by engineer. */ "Stop" = "Спри"; +/* No comment provided by engineer. */ +"Stop chat" = "Спри чата"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Спрете чата, за да активирате действията с базата данни"; @@ -3380,6 +3548,9 @@ /* authentication reason */ "Stop SimpleX" = "Спри SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Спиране на чата"; + /* No comment provided by engineer. */ "strike" = "зачеркнат"; @@ -3530,6 +3701,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Това действие не може да бъде отменено - вашият профил, контакти, съобщения и файлове ще бъдат безвъзвратно загубени."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Този чат е защитен чрез криптиране от край до край."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Този чат е защитен от квантово устойчиво криптиране от край до край."; + /* notification title */ "this contact" = "този контакт"; @@ -3722,9 +3899,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Актуализирай и отвори чата"; +/* No comment provided by engineer. */ +"Upload failed" = "Неуспешно качване"; + /* server test step */ "Upload file" = "Качи файл"; +/* No comment provided by engineer. */ +"Uploading archive" = "Архивът се качва"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Използвай .onion хостове"; @@ -3755,6 +3938,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Използвай сървърите на SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Използвайте приложението по време на разговора."; + /* No comment provided by engineer. */ "User profile" = "Потребителски профил"; @@ -3782,6 +3968,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Потвърждение за свързване"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Проверете паролата на базата данни"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Провери паролата"; + /* No comment provided by engineer. */ "Verify security code" = "Потвърди кода за сигурност"; @@ -3860,6 +4052,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "иска да се свърже с вас!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: стартирането на чата на множество устройства не се поддържа и ще доведе до неуспешно изпращане на съобщения"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Предупреждение: Може да загубите някои данни!"; @@ -3875,6 +4070,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Съобщение при посрещане"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Съобщението при посрещане е твърде дълго"; + /* No comment provided by engineer. */ "What's new" = "Какво е новото"; @@ -3971,6 +4169,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Можете да ги активирате по-късно през настройките за \"Поверителност и сигурност\" на приложението."; +/* No comment provided by engineer. */ +"You can give another try." = "Можете да опитате още веднъж."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Можете да скриете или заглушите известията за потребителски профил - плъзнете надясно."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 6eb9067684..e2cfcee022 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Warnung**: Das Archiv wird gelöscht."; + /* No comment provided by engineer. */ "*bold*" = "\\*fett*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ wurde mit Ihnen verbunden"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ heruntergeladen"; + /* notification title */ "%@ is connected!" = "%@ ist mit Ihnen verbunden!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@-Server"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ hochgeladen"; + /* notification title */ "%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "Admin"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Administratoren können ein Gruppenmitglied für Alle blockieren."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erzeugen."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen."; + /* No comment provided by engineer. */ "Allow" = "Erlauben"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "App Build: %@"; +/* No comment provided by engineer. */ +"App data migration" = "App-Daten-Migration"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Erscheinungsbild"; +/* No comment provided by engineer. */ +"Apply" = "Anwenden"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivieren und Hochladen"; + +/* No comment provided by engineer. */ +"Archiving database" = "Datenbank wird archiviert"; + /* No comment provided by engineer. */ "Attach" = "Anhängen"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Abbrechen"; +/* No comment provided by engineer. */ +"Cancel migration" = "Migration abbrechen"; + /* feature offered item */ "cancelled %@" = "abgebrochen %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Der Chat ist angehalten. Wenn Sie diese Datenbank bereits auf einem anderen Gerät genutzt haben, sollten Sie diese vor dem Starten des Chats wieder zurückspielen."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat wurde migriert!"; + /* No comment provided by engineer. */ "Chat preferences" = "Chat-Präferenzen"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chinesische und spanische Bedienoberfläche"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Wählen Sie auf dem neuen Gerät _Von einem anderen Gerät migrieren_ und scannen Sie den QR-Code."; + /* No comment provided by engineer. */ "Choose file" = "Datei auswählen"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Datenbank-Aktualisierungen bestätigen"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Bestätigen Sie die Netzwerkeinstellungen"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Neues Passwort bestätigen…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Passwort bestätigen"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Hochladen bestätigen"; + /* server test step */ "Connect" = "Verbinden"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Erstellt am %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Archiv-Link erzeugen"; + /* No comment provided by engineer. */ "Creating link…" = "Link wird erstellt…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Datenbank löschen"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Datenbank auf diesem Gerät löschen"; + /* server test step */ "Delete file" = "Datei löschen"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Datenbank herabstufen und den Chat öffnen"; +/* No comment provided by engineer. */ +"Download failed" = "Herunterladen fehlgeschlagen"; + /* server test step */ "Download file" = "Datei herunterladen"; +/* No comment provided by engineer. */ +"Downloading archive" = "Archiv wird heruntergeladen"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Link-Details werden heruntergeladen"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Doppelter Anzeigename!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Kann in direkten Chats aktiviert werden (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Sofortige Benachrichtigungen aktivieren?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Zugangscode eingeben"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Passwort eingeben"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Passwort eingeben…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Fehler beim Zulassen der Kontakt-PQ-Verschlüsselung"; + /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Fehler beim Löschen des Benutzerprofils"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Fehler beim Herunterladen des Archivs"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Fehler beim Aktivieren von Empfangsbestätigungen!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fehler beim Speichern des Passworts in den Schlüsselbund"; +/* when migrating */ +"Error saving settings" = "Fehler beim Abspeichern der Einstellungen"; + /* No comment provided by engineer. */ "Error saving user password" = "Fehler beim Speichern des Benutzer-Passworts"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Fehler beim Aktualisieren der Benutzer-Privatsphäre"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Fehler beim Hochladen des Archivs"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Fehler bei der Überprüfung des Passworts:"; + /* No comment provided by engineer. */ "Error: " = "Fehler: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Exportiertes Datenbankarchiv."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Die exportierte Datei ist nicht vorhanden"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Exportieren des Datenbank-Archivs…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Nach ungelesenen und favorisierten Chats filtern."; +/* No comment provided by engineer. */ +"Finalize migration" = "Die Migration wird abgeschlossen"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Die Migration auf dem anderen Gerät wird abgeschlossen."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Endlich haben wir sie! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Wie Sie Ihre Server nutzen"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Ungarische Bedienoberfläche"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE-Server (einer pro Zeile)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Datenbank importieren"; +/* No comment provided by engineer. */ +"Import failed" = "Import ist fehlgeschlagen"; + +/* No comment provided by engineer. */ +"Importing archive" = "Archiv wird importiert"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbesserte Zustellung von Nachrichten"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Verbesserte Serverkonfiguration"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Um fortzufahren, sollte der Chat beendet werden."; + /* No comment provided by engineer. */ "In reply to" = "Als Antwort auf"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ungültiger Link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Migrations-Bestätigung ungültig"; + /* No comment provided by engineer. */ "Invalid name!" = "Ungültiger Name!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Nachrichtentext"; +/* No comment provided by engineer. */ +"Message too large" = "Die Nachricht ist zu lang"; + /* No comment provided by engineer. */ "Messages" = "Nachrichten"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; + +/* No comment provided by engineer. */ +"Migrate device" = "Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Von einem anderen Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate here" = "Hierher migrieren"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Auf ein anderes Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Daten können über einen QR-Code auf ein anderes Gerät migriert werden."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrieren"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Datenbank-Archiv wird migriert…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migration abgeschlossen"; + /* No comment provided by engineer. */ "Migration error:" = "Fehler bei der Migration:"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Gruppe öffnen"; +/* authentication reason */ +"Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; + /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Oder fügen Sie den Archiv-Link ein"; + /* No comment provided by engineer. */ "Or scan QR code" = "Oder den QR-Code scannen"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Oder teilen Sie diesen Datei-Link sicher"; + /* No comment provided by engineer. */ "Or show this code" = "Oder diesen QR-Code anzeigen"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Entschlüsselungsfehler"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Bild-in-Bild-Anrufe"; + /* No comment provided by engineer. */ "PING count" = "PING-Zähler"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Bitte überprüfen sie sowohl Ihre, als auch die Präferenzen Ihres Kontakts."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Bitte nehmen Sie Kontakt mit den Entwicklern auf.\nFehler: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Post-Quantum E2E-Verschlüsselung"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; +/* No comment provided by engineer. */ +"Push server" = "Push-Server"; + +/* chat item text */ +"quantum resistant e2e encryption" = "Quantum-resistente E2E-Verschlüsselung"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Quantum-resistente Verschlüsselung"; + /* No comment provided by engineer. */ "Rate the app" = "Bewerten Sie die App"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Verbindungsanfrage wiederholen?"; +/* No comment provided by engineer. */ +"Repeat download" = "Herunterladen wiederholen"; + +/* No comment provided by engineer. */ +"Repeat import" = "Import wiederholen"; + /* No comment provided by engineer. */ "Repeat join request?" = "Verbindungsanfrage wiederholen?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Hochladen wiederholen"; + /* chat item action */ "Reply" = "Antwort"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat starten"; +/* No comment provided by engineer. */ +"Safer groups" = "Sicherere Gruppen"; + /* chat item action */ "Save" = "Speichern"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Zugangscode einstellen"; +/* No comment provided by engineer. */ +"Set passphrase" = "Passwort festlegen"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Passwort für den Export festlegen"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Vorschau anzeigen"; +/* No comment provided by engineer. */ +"Show QR code" = "QR-Code anzeigen"; + /* No comment provided by engineer. */ "Show:" = "Anzeigen:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Jemand"; +/* chat item text */ +"standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung"; + /* No comment provided by engineer. */ "Start chat" = "Starten Sie den Chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Beenden"; +/* No comment provided by engineer. */ +"Stop chat" = "Chat beenden"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Chat beenden, um Datenbankaktionen zu erlauben"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Stoppen Sie SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Chat wird beendet"; + /* No comment provided by engineer. */ "strike" = "durchstreichen"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Diese Aktion kann nicht rückgängig gemacht werden! Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt."; + /* notification title */ "this contact" = "Dieser Kontakt"; @@ -3555,7 +3753,7 @@ "This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**."; /* No comment provided by engineer. */ -"To ask any questions and to receive updates:" = "Um Fragen zu stellen und Aktualisierungen zu erhalten:"; +"To ask any questions and to receive updates:" = "Um Fragen zu stellen und aktuelle Informationen zu erhalten:"; /* No comment provided by engineer. */ "To connect, your contact can scan QR code or use the link in the app." = "Um eine Verbindung herzustellen, kann Ihr Kontakt den QR-Code scannen oder den Link in der App verwenden."; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aktualisieren und den Chat öffnen"; +/* No comment provided by engineer. */ +"Upload failed" = "Hochladen fehlgeschlagen"; + /* server test step */ "Upload file" = "Datei hochladen"; +/* No comment provided by engineer. */ +"Uploading archive" = "Archiv wird hochgeladen"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Verwende .onion-Hosts"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden."; + /* No comment provided by engineer. */ "User profile" = "Benutzerprofil"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Verbindungen überprüfen"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Überprüfen Sie das Datenbank-Passwort"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Überprüfen Sie das Passwort"; + /* No comment provided by engineer. */ "Verify security code" = "Sicherheitscode überprüfen"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "möchte sich mit Ihnen verbinden!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Warnung: Sie könnten einige Daten verlieren!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Begrüßungsmeldung"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Die Begrüßungsmeldung ist zu lang"; + /* No comment provided by engineer. */ "What's new" = "Was ist neu"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Profil"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen."; + /* No comment provided by engineer. */ "You accepted connection" = "Sie haben die Verbindung akzeptiert"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren."; +/* No comment provided by engineer. */ +"You can give another try." = "Sie können es nochmal probieren."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts."; @@ -4134,7 +4359,7 @@ "Your contacts can allow full message deletion." = "Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben."; /* No comment provided by engineer. */ -"Your contacts will remain connected." = "Ihre Kontakte bleiben verbunden."; +"Your contacts will remain connected." = "Ihre Kontakte bleiben weiterhin verbunden."; /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index ad252b0a88..7e82b77e2e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Tenga en cuenta**: usar la misma base de datos en dos dispositivos interrumpirá el descifrado de mensajes de sus conexiones, como protección de seguridad."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Atención**: el archivo será eliminado."; + /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ conectado"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ downloaded"; + /* notification title */ "%@ is connected!" = "%@ ¡está conectado!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "Servidores %@"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ cargado"; + /* notification title */ "%@ wants to connect!" = "¡ %@ quiere contactar!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "administrador"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Los admins pueden bloquear un miembro por todos."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Los administradores pueden crear enlaces para unirse a grupos."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Todos tus contactos permanecerán conectados. La actualización del perfil se enviará a tus contactos."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Todos sus contactos, conversaciones y archivos se cifrarán de forma segura y se subirán en fragmentos hacia puntos XFTP configurados."; + /* No comment provided by engineer. */ "Allow" = "Se permite"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Compilación app: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migración de datos de la aplicación"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Cifrado de los nuevos archivos locales (excepto vídeos)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Apariencia"; +/* No comment provided by engineer. */ +"Apply" = "Aplicar"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivar y transferir"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archivando base de datos"; + /* No comment provided by engineer. */ "Attach" = "Adjuntar"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Cancelar"; +/* No comment provided by engineer. */ +"Cancel migration" = "Cancelar migración"; + /* feature offered item */ "cancelled %@" = "cancelado %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Chat está detenido. Si estás usando esta base de datos en otro dispositivo, deberías transferirla de vuelta antes de iniciarlo."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat transferido !"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferencias de Chat"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interfaz en chino y español"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Elija _Migrar desde otro dispositivo_ en el nuevo dispositivo y escanee el código QR."; + /* No comment provided by engineer. */ "Choose file" = "Elije archivo"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmar actualizaciones de la bases de datos"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Confirmar los ajustes de red"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Confirme nueva contraseña…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Confirmar contraseña"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Confirme que recuerda la frase secreta de la base de datos para migrarla."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Confirmar subida"; + /* server test step */ "Connect" = "Conectar"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Creado en %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Creando enlace de archivo"; + /* No comment provided by engineer. */ "Creating link…" = "Creando enlace…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Eliminar base de datos"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Eliminar base de datos de este dispositivo"; + /* server test step */ "Delete file" = "Eliminar archivo"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Degradar y abrir Chat"; +/* No comment provided by engineer. */ +"Download failed" = "Error en la descarga"; + /* server test step */ "Download file" = "Descargar archivo"; +/* No comment provided by engineer. */ +"Downloading archive" = "Descargando archivo"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Descargando detalles del enlace"; + /* No comment provided by engineer. */ "Duplicate display name!" = "¡Nombre mostrado duplicado!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Activar en chats directos (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "¿Activar notificación instantánea?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Introduce Código"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Introducir frase de contraseña"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Introduce la contraseña…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Error al permitir cifrado PQ al contacto"; + /* No comment provided by engineer. */ "Error changing address" = "Error al cambiar servidor"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Error al eliminar perfil"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Error al descargar el archivo"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "¡Error al activar confirmaciones de entrega!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Error al guardar contraseña en Keychain"; +/* when migrating */ +"Error saving settings" = "Error al guardar ajustes"; + /* No comment provided by engineer. */ "Error saving user password" = "Error al guardar contraseña de usuario"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Error al actualizar privacidad de usuario"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Error al subir el archivo"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Error al verificar la frase de contraseña:"; + /* No comment provided by engineer. */ "Error: " = "Error: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Archivo de base de datos exportado."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "El archivo exportado no existe"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Exportando base de datos…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtra chats no leídos y favoritos."; +/* No comment provided by engineer. */ +"Finalize migration" = "Finalizar la migración"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Finalizar la migración en otro dispositivo."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "¡Por fin los tenemos! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Cómo usar los servidores"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Interfaz húngara"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Servidores ICE (uno por línea)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importar base de datos"; +/* No comment provided by engineer. */ +"Import failed" = "Error de importación"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importando archivo"; + /* No comment provided by engineer. */ "Improved message delivery" = "Entrega de mensajes mejorada"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Configuración del servidor mejorada"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Para continuar, el chat debe ser interrumpido."; + /* No comment provided by engineer. */ "In reply to" = "En respuesta a"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Enlace no válido"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Confirmación de migración inválida"; + /* No comment provided by engineer. */ "Invalid name!" = "¡Nombre no válido!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Contacto y texto"; +/* No comment provided by engineer. */ +"Message too large" = "Mensaje demasiado grande"; + /* No comment provided by engineer. */ "Messages" = "Mensajes"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "¡Los mensajes de %@ serán mostrados!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo** con perfecta confidencialidad, repudio y recuperación tras ataques."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo resistente a la computación cuántica** con perfecta confidencialidad, repudio y recuperación tras ataques."; + +/* No comment provided by engineer. */ +"Migrate device" = "Migrar dispositivo"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Migrar desde otro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate here" = "Migrar aquí"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Migrar hacia otro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Migrar hacia otro dispositivo mediante código QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrando"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migrando base de datos…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migración completada"; + /* No comment provided by engineer. */ "Migration error:" = "Error de migración:"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Grupo abierto"; +/* authentication reason */ +"Open migration to another device" = "Abrir la migración hacia otro dispositivo"; + /* No comment provided by engineer. */ "Open Settings" = "Abrir Configuración"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "O pegar enlace del archivo"; + /* No comment provided by engineer. */ "Or scan QR code" = "O escanear código QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "O comparta de forma segura el enlace de este archivo"; + /* No comment provided by engineer. */ "Or show this code" = "O mostrar este código"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Error permanente descifrado"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Llamadas picture-in-picture"; + /* No comment provided by engineer. */ "PING count" = "Contador PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Comprueba tus preferencias y las de tu contacto."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Por favor confirme que la configuración de red es correcta para este dispositivo."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Por favor, contacta con los desarrolladores.\nError: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "E2EE postcuántica"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Notificaciones automáticas"; +/* No comment provided by engineer. */ +"Push server" = "Servidor push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "cifrado e2e resistente a la cuántica"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Cifrado resistente a la tecnología cuántica"; + /* No comment provided by engineer. */ "Rate the app" = "Valora la aplicación"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "¿Repetir solicitud de conexión?"; +/* No comment provided by engineer. */ +"Repeat download" = "Repetir descarga"; + +/* No comment provided by engineer. */ +"Repeat import" = "Repetir importación"; + /* No comment provided by engineer. */ "Repeat join request?" = "¿Repetir solicitud de admisión?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Repetir la carga"; + /* chat item action */ "Reply" = "Responder"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Ejecutar chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Grupos más seguros"; + /* chat item action */ "Save" = "Guardar"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Código autodestrucción"; +/* No comment provided by engineer. */ +"Set passphrase" = "Definir frase de contraseña"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Escribe la contraseña para exportar"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Mostrar vista previa"; +/* No comment provided by engineer. */ +"Show QR code" = "Mostrar código QR"; + /* No comment provided by engineer. */ "Show:" = "Mostrar:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Alguien"; +/* chat item text */ +"standard end-to-end encryption" = "cifrado estándar de extremo a extremo"; + /* No comment provided by engineer. */ "Start chat" = "Iniciar chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Detener"; +/* No comment provided by engineer. */ +"Stop chat" = "Detener el chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Detén SimpleX para habilitar las acciones sobre la base de datos"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Detener SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Detención del chat"; + /* No comment provided by engineer. */ "strike" = "tachado"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Este chat está protegido por cifrado de extremo a extremo."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Este chat está protegido por un cifrado de extremo a extremo resistente a tecnologías cuánticas."; + /* notification title */ "this contact" = "este contacto"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Actualizar y abrir Chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Error de carga"; + /* server test step */ "Upload file" = "Subir archivo"; +/* No comment provided by engineer. */ +"Uploading archive" = "Subiendo el archivo"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Usar hosts .onion"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Usar la app durante la llamada."; + /* No comment provided by engineer. */ "User profile" = "Perfil de usuario"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Verificar conexiones"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Verificar la contraseña de la base de datos"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Verificar frase de contraseña"; + /* No comment provided by engineer. */ "Verify security code" = "Comprobar código de seguridad"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "¡quiere contactar contigo!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Advertencia: el inicio del chat en varios dispositivos no es compatible y provocará fallos en la entrega de mensajes"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Atención: ¡puedes perder algunos datos!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Mensaje de bienvenida"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "El mensaje de bienvenida es demasiado largo"; + /* No comment provided by engineer. */ "What's new" = "Novedades"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Tú"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "**No debe** usar la misma base de datos en dos dispositivos."; + /* No comment provided by engineer. */ "You accepted connection" = "Has aceptado la conexión"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Puedes activarlos más tarde en la configuración de Privacidad y Seguridad."; +/* No comment provided by engineer. */ +"You can give another try." = "Puede intentarlo de nuevo."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Puedes ocultar o silenciar un perfil deslizándolo a la derecha."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 4417eb35a1..cbc72479e3 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Avertissement** : l'archive sera supprimée."; + /* No comment provided by engineer. */ "*bold*" = "\\*gras*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ connecté(e)"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ téléchargé"; + /* notification title */ "%@ is connected!" = "%@ est connecté·e !"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "Serveurs %@"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ envoyé"; + /* notification title */ "%@ wants to connect!" = "%@ veut se connecter !"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "admin"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Les admins peuvent bloquer un membre pour tous."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Les admins peuvent créer les liens qui permettent de rejoindre les groupes."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Tous vos contacts resteront connectés. La mise à jour du profil sera envoyée à vos contacts."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés."; + /* No comment provided by engineer. */ "Allow" = "Autoriser"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Build de l'app : %@"; +/* No comment provided by engineer. */ +"App data migration" = "Transfert des données de l'application"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "L'application chiffre les nouveaux fichiers locaux (sauf les vidéos)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Apparence"; +/* No comment provided by engineer. */ +"Apply" = "Appliquer"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiver et transférer"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archivage de la base de données"; + /* No comment provided by engineer. */ "Attach" = "Attacher"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annuler"; +/* No comment provided by engineer. */ +"Cancel migration" = "Annuler le transfert"; + /* feature offered item */ "cancelled %@" = "annulé %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Le chat est arrêté. Si vous avez déjà utilisé cette base de données sur un autre appareil, vous devez la transférer à nouveau avant de démarrer le chat."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Messagerie transférée !"; + /* No comment provided by engineer. */ "Chat preferences" = "Préférences de chat"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interface en chinois et en espagnol"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Choisissez _Transferer depuis un autre appareil_ sur le nouvel appareil et scannez le code QR."; + /* No comment provided by engineer. */ "Choose file" = "Choisir le fichier"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmer la mise à niveau de la base de données"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Confirmer les paramètres réseau"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Confirmer la nouvelle phrase secrète…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Confirmer le mot de passe"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Confirmer la transmission"; + /* server test step */ "Connect" = "Se connecter"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Créé le %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Création d'un lien d'archive"; + /* No comment provided by engineer. */ "Creating link…" = "Création d'un lien…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Supprimer la base de données"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Supprimer la base de données de cet appareil"; + /* server test step */ "Delete file" = "Supprimer le fichier"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; +/* No comment provided by engineer. */ +"Download failed" = "Échec du téléchargement"; + /* server test step */ "Download file" = "Télécharger le fichier"; +/* No comment provided by engineer. */ +"Downloading archive" = "Téléchargement de l'archive"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Téléchargement des détails du lien"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Nom d'affichage en double !"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Activer pour tous"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Activer dans les conversations directes (BETA) !"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Activer les notifications instantanées ?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Entrer le code d'accès"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Entrer la phrase secrète"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Entrez la phrase secrète…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Erreur lors de l'ajout de membre·s"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Erreur lors de la négociation du chiffrement PQ"; + /* No comment provided by engineer. */ "Error changing address" = "Erreur de changement d'adresse"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Erreur lors de la suppression du profil utilisateur"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Erreur lors du téléchargement de l'archive"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Erreur lors de l'activation des accusés de réception !"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Erreur lors de l'enregistrement de la phrase de passe dans la keychain"; +/* when migrating */ +"Error saving settings" = "Erreur lors de l'enregistrement des paramètres"; + /* No comment provided by engineer. */ "Error saving user password" = "Erreur d'enregistrement du mot de passe de l'utilisateur"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Erreur de mise à jour de la confidentialité de l'utilisateur"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Erreur lors de l'envoi de l'archive"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Erreur lors de la vérification de la phrase secrète :"; + /* No comment provided by engineer. */ "Error: " = "Erreur : "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Archive de la base de données exportée."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Le fichier exporté n'existe pas"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Exportation de l'archive de la base de données…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtrer les messages non lus et favoris."; +/* No comment provided by engineer. */ +"Finalize migration" = "Finaliser le transfert"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Finalisez le transfert sur l'autre appareil."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Enfin, les voilà ! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Comment utiliser vos serveurs"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Interface en hongrois"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Serveurs ICE (un par ligne)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importer la base de données"; +/* No comment provided by engineer. */ +"Import failed" = "Échec de l'importation"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importation de l'archive"; + /* No comment provided by engineer. */ "Improved message delivery" = "Amélioration de la transmission des messages"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Configuration de serveur améliorée"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Pour continuer, le chat doit être interrompu."; + /* No comment provided by engineer. */ "In reply to" = "En réponse à"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Lien invalide"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Confirmation de migration invalide"; + /* No comment provided by engineer. */ "Invalid name!" = "Nom invalide !"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Texte du message"; +/* No comment provided by engineer. */ +"Message too large" = "Message trop volumineux"; + /* No comment provided by engineer. */ "Messages" = "Messages"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Les messages de %@ seront affichés !"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; + +/* No comment provided by engineer. */ +"Migrate device" = "Transférer l'appareil"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Transférer depuis un autre appareil"; + +/* No comment provided by engineer. */ +"Migrate here" = "Transférer ici"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Transférer vers un autre appareil"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Transférer vers un autre appareil via un code QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Transfert"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migration de l'archive de la base de données…"; +/* No comment provided by engineer. */ +"Migration complete" = "Transfert terminé"; + /* No comment provided by engineer. */ "Migration error:" = "Erreur de migration :"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Ouvrir le groupe"; +/* authentication reason */ +"Open migration to another device" = "Ouvrir le transfert vers un autre appareil"; + /* No comment provided by engineer. */ "Open Settings" = "Ouvrir les Paramètres"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Ouverture de l'app…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Ou coller le lien de l'archive"; + /* No comment provided by engineer. */ "Or scan QR code" = "Ou scanner le code QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Ou partagez en toute sécurité le lien de ce fichier"; + /* No comment provided by engineer. */ "Or show this code" = "Ou présenter ce code"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Erreur de déchiffrement"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Appels picture-in-picture"; + /* No comment provided by engineer. */ "PING count" = "Nombre de PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Veuillez vérifier vos préférences ainsi que celles de votre contact."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Veuillez confirmer que les paramètres réseau de cet appareil sont corrects."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Veuillez contacter les développeurs.\nErreur : %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "E2EE post-quantique"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserver le brouillon du dernier message, avec les pièces jointes."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifications push"; +/* No comment provided by engineer. */ +"Push server" = "Serveur Push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "chiffrement e2e résistant post-quantique"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Chiffrement résistant post-quantique"; + /* No comment provided by engineer. */ "Rate the app" = "Évaluer l'app"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Répéter la demande de connexion ?"; +/* No comment provided by engineer. */ +"Repeat download" = "Répéter le téléchargement"; + +/* No comment provided by engineer. */ +"Repeat import" = "Répéter l'importation"; + /* No comment provided by engineer. */ "Repeat join request?" = "Répéter la requête d'adhésion ?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Répéter l'envoi"; + /* chat item action */ "Reply" = "Répondre"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Exécuter le chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Groupes plus sûrs"; + /* chat item action */ "Save" = "Enregistrer"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Définir le code d'accès"; +/* No comment provided by engineer. */ +"Set passphrase" = "Définir une phrase secrète"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Définir la phrase secrète pour l'export"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Afficher l'aperçu"; +/* No comment provided by engineer. */ +"Show QR code" = "Afficher le code QR"; + /* No comment provided by engineer. */ "Show:" = "Afficher :"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Quelqu'un"; +/* chat item text */ +"standard end-to-end encryption" = "chiffrement de bout en bout standard"; + /* No comment provided by engineer. */ "Start chat" = "Démarrer le chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Arrêter"; +/* No comment provided by engineer. */ +"Stop chat" = "Arrêter le chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Arrêter le chat pour permettre des actions sur la base de données"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Arrêter SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Arrêt du chat"; + /* No comment provided by engineer. */ "strike" = "barré"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Cette action ne peut être annulée - votre profil, vos contacts, vos messages et vos fichiers seront irréversiblement perdus."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout résistant aux technologies quantiques."; + /* notification title */ "this contact" = "ce contact"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Mettre à niveau et ouvrir le chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Échec de l'envoi"; + /* server test step */ "Upload file" = "Transférer le fichier"; +/* No comment provided by engineer. */ +"Uploading archive" = "Envoi de l'archive"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Utiliser les hôtes .onions"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Utiliser les serveurs SimpleX Chat ?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Utiliser l'application pendant l'appel."; + /* No comment provided by engineer. */ "User profile" = "Profil d'utilisateur"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Vérifier les connexions"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Vérifier la phrase secrète de la base de données"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Vérifier la phrase secrète"; + /* No comment provided by engineer. */ "Verify security code" = "Vérifier le code de sécurité"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "veut établir une connexion !"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attention: démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Attention : vous risquez de perdre des données !"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Message de bienvenue"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Le message de bienvenue est trop long"; + /* No comment provided by engineer. */ "What's new" = "Quoi de neuf ?"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Vous"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Vous **ne devez pas** utiliser la même base de données sur deux appareils."; + /* No comment provided by engineer. */ "You accepted connection" = "Vous avez accepté la connexion"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application."; +/* No comment provided by engineer. */ +"You can give another try." = "Vous pouvez faire un nouvel essai."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Vous pouvez masquer ou mettre en sourdine un profil d'utilisateur - faites-le glisser vers la droite."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index c105bb1fa1..5ebda7396f 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Nota bene**: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Nota bene**: NON potrai recuperare o cambiare la password se la perdi."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Attenzione**: l'archivio verrà rimosso."; + /* No comment provided by engineer. */ "*bold*" = "\\*grassetto*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ si è connesso/a"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ scaricati"; + /* notification title */ "%@ is connected!" = "%@ è connesso/a!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "Server %@"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ caricati"; + /* notification title */ "%@ wants to connect!" = "%@ si vuole connettere!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "amministratore"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Gli amministratori possono bloccare un membro per tutti."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Gli amministratori possono creare i link per entrare nei gruppi."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Tutti i tuoi contatti resteranno connessi. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati."; + /* No comment provided by engineer. */ "Allow" = "Consenti"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Build dell'app: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migrazione dati dell'app"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "L'app cripta i nuovi file locali (eccetto i video)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Aspetto"; +/* No comment provided by engineer. */ +"Apply" = "Applica"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivia e carica"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archiviazione del database"; + /* No comment provided by engineer. */ "Attach" = "Allega"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annulla"; +/* No comment provided by engineer. */ +"Cancel migration" = "Annulla migrazione"; + /* feature offered item */ "cancelled %@" = "annullato %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "La chat è ferma. Se hai già usato questo database su un altro dispositivo, dovresti trasferirlo prima di avviare la chat."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat migrata!"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferenze della chat"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interfaccia cinese e spagnola"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Scegli _Migra da un altro dispositivo_ sul nuovo dispositivo e scansione il codice QR."; + /* No comment provided by engineer. */ "Choose file" = "Scegli file"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Conferma aggiornamenti database"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Conferma le impostazioni di rete"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Conferma nuova password…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Conferma password"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Conferma che ricordi la password del database da migrare."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Conferma caricamento"; + /* server test step */ "Connect" = "Connetti"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Creato il %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Creazione link dell'archivio"; + /* No comment provided by engineer. */ "Creating link…" = "Creazione link…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Elimina database"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Elimina il database da questo dispositivo"; + /* server test step */ "Delete file" = "Elimina file"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Esegui downgrade e apri chat"; +/* No comment provided by engineer. */ +"Download failed" = "Scaricamento fallito"; + /* server test step */ "Download file" = "Scarica file"; +/* No comment provided by engineer. */ +"Downloading archive" = "Scaricamento archivio"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Scaricamento dettagli del link"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Nome da mostrare doppio!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Attiva per tutti"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Attivala nelle chat dirette (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Attivare le notifiche istantanee?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Inserisci il codice di accesso"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Inserisci password"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Inserisci la password…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Errore nel consentire la crittografia PQ al contatto"; + /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Errore nell'eliminazione del profilo utente"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Errore di scaricamento dell'archivio"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Errore nell'attivazione delle ricevute di consegna!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Errore nel salvataggio della password nel portachiavi"; +/* when migrating */ +"Error saving settings" = "Errore di salvataggio delle impostazioni"; + /* No comment provided by engineer. */ "Error saving user password" = "Errore nel salvataggio della password utente"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Errore nell'aggiornamento della privacy dell'utente"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Errore di invio dell'archivio"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Errore di verifica della password:"; + /* No comment provided by engineer. */ "Error: " = "Errore: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Archivio database esportato."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Il file esportato non esiste"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Esportazione archivio database…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtra le chat non lette e preferite."; +/* No comment provided by engineer. */ +"Finalize migration" = "Finalizza la migrazione"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Finalizza la migrazione su un altro dispositivo."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Finalmente le abbiamo! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Come usare i tuoi server"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Interfaccia in ungherese"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Server ICE (uno per riga)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importa database"; +/* No comment provided by engineer. */ +"Import failed" = "Importazione fallita"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importazione archivio"; + /* No comment provided by engineer. */ "Improved message delivery" = "Consegna dei messaggi migliorata"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Configurazione del server migliorata"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Per continuare, la chat deve essere fermata."; + /* No comment provided by engineer. */ "In reply to" = "In risposta a"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Link non valido"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Conferma di migrazione non valida"; + /* No comment provided by engineer. */ "Invalid name!" = "Nome non valido!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Testo del messaggio"; +/* No comment provided by engineer. */ +"Message too large" = "Messaggio troppo grande"; + /* No comment provided by engineer. */ "Messages" = "Messaggi"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione."; + +/* No comment provided by engineer. */ +"Migrate device" = "Migra dispositivo"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Migra da un altro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate here" = "Migra qui"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Migra ad un altro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Migra ad un altro dispositivo via codice QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrazione"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migrazione archivio del database…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migrazione completata"; + /* No comment provided by engineer. */ "Migration error:" = "Errore di migrazione:"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Apri gruppo"; +/* authentication reason */ +"Open migration to another device" = "Apri migrazione ad un altro dispositivo"; + /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "O incolla il link dell'archivio"; + /* No comment provided by engineer. */ "Or scan QR code" = "O scansiona il codice QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "O condividi in modo sicuro questo link del file"; + /* No comment provided by engineer. */ "Or show this code" = "O mostra questo codice"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Errore di decifrazione"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Chiamate picture-in-picture"; + /* No comment provided by engineer. */ "PING count" = "Conteggio PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Controlla le preferenze tue e del tuo contatto."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Conferma che le impostazioni di rete sono corrette per questo dispositivo."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Contatta gli sviluppatori.\nErrore: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "E2EE post-quantistica"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; +/* No comment provided by engineer. */ +"Push server" = "Server push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "crittografia e2e resistente alla quantistica"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Crittografia resistente alla quantistica"; + /* No comment provided by engineer. */ "Rate the app" = "Valuta l'app"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Ripetere la richiesta di connessione?"; +/* No comment provided by engineer. */ +"Repeat download" = "Ripeti scaricamento"; + +/* No comment provided by engineer. */ +"Repeat import" = "Ripeti importazione"; + /* No comment provided by engineer. */ "Repeat join request?" = "Ripetere la richiesta di ingresso?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Ripeti caricamento"; + /* chat item action */ "Reply" = "Rispondi"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Avvia chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Gruppi più sicuri"; + /* chat item action */ "Save" = "Salva"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Imposta codice"; +/* No comment provided by engineer. */ +"Set passphrase" = "Imposta password"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Imposta la password per esportare"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Mostra anteprima"; +/* No comment provided by engineer. */ +"Show QR code" = "Mostra codice QR"; + /* No comment provided by engineer. */ "Show:" = "Mostra:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Qualcuno"; +/* chat item text */ +"standard end-to-end encryption" = "crittografia end-to-end standard"; + /* No comment provided by engineer. */ "Start chat" = "Avvia chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Ferma"; +/* No comment provided by engineer. */ +"Stop chat" = "Ferma la chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Ferma la chat per attivare le azioni del database"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Ferma SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Arresto della chat"; + /* No comment provided by engineer. */ "strike" = "barrato"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Questa azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end resistente alla quantistica."; + /* notification title */ "this contact" = "questo contatto"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aggiorna e apri chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Invio fallito"; + /* server test step */ "Upload file" = "Invia file"; +/* No comment provided by engineer. */ +"Uploading archive" = "Invio dell'archivio"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Usa gli host .onion"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Usare i server di SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Usa l'app mentre sei in chiamata."; + /* No comment provided by engineer. */ "User profile" = "Profilo utente"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Verifica le connessioni"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Verifica password del database"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Verifica password"; + /* No comment provided by engineer. */ "Verify security code" = "Verifica codice di sicurezza"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "vuole connettersi con te!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Attenzione: potresti perdere alcuni dati!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Messaggio di benvenuto"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Il messaggio di benvenuto è troppo lungo"; + /* No comment provided by engineer. */ "What's new" = "Novità"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Tu"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "**Non devi** usare lo stesso database su due dispositivi."; + /* No comment provided by engineer. */ "You accepted connection" = "Hai accettato la connessione"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Puoi attivarle più tardi nelle impostazioni di privacy e sicurezza dell'app."; +/* No comment provided by engineer. */ +"You can give another try." = "Puoi fare un altro tentativo."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Puoi nascondere o silenziare un profilo utente - scorrilo verso destra."; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 7c4f487f99..6ba5b419f6 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Let op**: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Waarschuwing**: het archief wordt verwijderd."; + /* No comment provided by engineer. */ "*bold*" = "\\*vetgedrukt*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ verbonden"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ gedownload"; + /* notification title */ "%@ is connected!" = "%@ is verbonden!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ servers"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ geüpload"; + /* notification title */ "%@ wants to connect!" = "%@ wil verbinding maken!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "Beheerder"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Beheerders kunnen een lid voor iedereen blokkeren."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Beheerders kunnen de uitnodiging links naar groepen aanmaken."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Al uw contacten blijven verbonden. Profiel update wordt naar uw contacten verzonden."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays."; + /* No comment provided by engineer. */ "Allow" = "Toestaan"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "App build: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migratie van app-gegevens"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "App versleutelt nieuwe lokale bestanden (behalve video's)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Uiterlijk"; +/* No comment provided by engineer. */ +"Apply" = "Toepassen"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiveren en uploaden"; + +/* No comment provided by engineer. */ +"Archiving database" = "Database archiveren"; + /* No comment provided by engineer. */ "Attach" = "Bijvoegen"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annuleren"; +/* No comment provided by engineer. */ +"Cancel migration" = "Migratie annuleren"; + /* feature offered item */ "cancelled %@" = "geannuleerd %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Chat is gestopt. Als je deze database al op een ander apparaat hebt gebruikt, moet je deze terugzetten voordat je met chatten begint."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat gemigreerd!"; + /* No comment provided by engineer. */ "Chat preferences" = "Gesprek voorkeuren"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chinese en Spaanse interface"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Kies _Migreren vanaf een ander apparaat_ op het nieuwe apparaat en scan de QR-code."; + /* No comment provided by engineer. */ "Choose file" = "Kies bestand"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Bevestig database upgrades"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Bevestig netwerk instellingen"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Bevestig nieuw wachtwoord…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Bevestig wachtwoord"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Bevestig het uploaden"; + /* server test step */ "Connect" = "Verbind"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Gemaakt op %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Archief link maken"; + /* No comment provided by engineer. */ "Creating link…" = "Link maken…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Database verwijderen"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Verwijder de database van dit apparaat"; + /* server test step */ "Delete file" = "Verwijder bestand"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Downgraden en chat openen"; +/* No comment provided by engineer. */ +"Download failed" = "Download mislukt"; + /* server test step */ "Download file" = "Download bestand"; +/* No comment provided by engineer. */ +"Downloading archive" = "Archief downloaden"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Link gegevens downloaden"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Dubbele weergavenaam!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Inschakelen voor iedereen"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Activeer in directe chats (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Onmiddellijke meldingen inschakelen?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Voer toegangscode in"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Voer het wachtwoord in"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Voer wachtwoord in…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Fout bij het toevoegen van leden"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Fout bij het toestaan van contact PQ-versleuteling"; + /* No comment provided by engineer. */ "Error changing address" = "Fout bij wijzigen van adres"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Fout bij het verwijderen van gebruikers profiel"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Fout bij het downloaden van het archief"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Fout bij het inschakelen van ontvangst bevestiging!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fout bij opslaan van wachtwoord in de keychain"; +/* when migrating */ +"Error saving settings" = "Fout bij opslaan van instellingen"; + /* No comment provided by engineer. */ "Error saving user password" = "Fout bij opslaan gebruikers wachtwoord"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Fout bij updaten van gebruikers privacy"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Fout bij het uploaden van het archief"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Fout bij het verifiëren van het wachtwoord:"; + /* No comment provided by engineer. */ "Error: " = "Fout: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Geëxporteerd database archief."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Geëxporteerd bestand bestaat niet"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Database archief exporteren…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filter ongelezen en favoriete chats."; +/* No comment provided by engineer. */ +"Finalize migration" = "Voltooi de migratie"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Voltooi de migratie op een ander apparaat."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Eindelijk, we hebben ze! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Hoe u uw servers gebruikt"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Hongaarse interface"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE servers (één per lijn)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Database importeren"; +/* No comment provided by engineer. */ +"Import failed" = "Importeren is mislukt"; + +/* No comment provided by engineer. */ +"Importing archive" = "Archief importeren"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbeterde berichtbezorging"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Verbeterde serverconfiguratie"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Om verder te kunnen gaan, moet de chat worden gestopt."; + /* No comment provided by engineer. */ "In reply to" = "In antwoord op"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ongeldige link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Ongeldige migratie bevestiging"; + /* No comment provided by engineer. */ "Invalid name!" = "Ongeldige naam!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Bericht tekst"; +/* No comment provided by engineer. */ +"Message too large" = "Bericht te groot"; + /* No comment provided by engineer. */ "Messages" = "Berichten"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Berichten van %@ worden getoond!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel."; + +/* No comment provided by engineer. */ +"Migrate device" = "Apparaat migreren"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Migreer vanaf een ander apparaat"; + +/* No comment provided by engineer. */ +"Migrate here" = "Migreer hierheen"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Migreer naar een ander apparaat"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Migreer naar een ander apparaat via QR-code."; + +/* No comment provided by engineer. */ +"Migrating" = "Migreren"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Database archief migreren…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migratie voltooid"; + /* No comment provided by engineer. */ "Migration error:" = "Migratiefout:"; @@ -2592,7 +2730,7 @@ "Open" = "Open"; /* No comment provided by engineer. */ -"Open chat" = "Gesprekken openen"; +"Open chat" = "Chat openen"; /* authentication reason */ "Open chat console" = "Chat console openen"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Open groep"; +/* authentication reason */ +"Open migration to another device" = "Open de migratie naar een ander apparaat"; + /* No comment provided by engineer. */ "Open Settings" = "Open instellingen"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "App openen…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Of plak de archief link"; + /* No comment provided by engineer. */ "Or scan QR code" = "Of scan de QR-code"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Of deel deze bestands link veilig"; + /* No comment provided by engineer. */ "Or show this code" = "Of laat deze code zien"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Decodering fout"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Beeld-in-beeld oproepen"; + /* No comment provided by engineer. */ "PING count" = "PING count"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Controleer de uwe en uw contact voorkeuren."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Controleer of de netwerk instellingen correct zijn voor dit apparaat."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Neem contact op met ontwikkelaars.\nFout: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Mogelijk is de certificaat vingerafdruk in het server adres onjuist"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Post-quantum E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Bewaar het laatste berichtconcept, met bijlagen."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Push meldingen"; +/* No comment provided by engineer. */ +"Push server" = "Push server"; + +/* chat item text */ +"quantum resistant e2e encryption" = "quantum bestendige e2e-codering"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "quantum bestendige encryptie"; + /* No comment provided by engineer. */ "Rate the app" = "Beoordeel de app"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Verbindingsverzoek herhalen?"; +/* No comment provided by engineer. */ +"Repeat download" = "Herhaal het downloaden"; + +/* No comment provided by engineer. */ +"Repeat import" = "Herhaal import"; + /* No comment provided by engineer. */ "Repeat join request?" = "Deelnameverzoek herhalen?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Herhaal het uploaden"; + /* chat item action */ "Reply" = "Antwoord"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat uitvoeren"; +/* No comment provided by engineer. */ +"Safer groups" = "Veiligere groepen"; + /* chat item action */ "Save" = "Opslaan"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Toegangscode instellen"; +/* No comment provided by engineer. */ +"Set passphrase" = "Wachtwoord instellen"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Wachtwoord instellen om te exporteren"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Toon voorbeeld"; +/* No comment provided by engineer. */ +"Show QR code" = "Toon QR-code"; + /* No comment provided by engineer. */ "Show:" = "Toon:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Iemand"; +/* chat item text */ +"standard end-to-end encryption" = "standaard end-to-end encryptie"; + /* No comment provided by engineer. */ "Start chat" = "Begin gesprek"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Stop"; +/* No comment provided by engineer. */ +"Stop chat" = "Stop chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Stop de chat om database acties mogelijk te maken"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Stop SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Chat stoppen"; + /* No comment provided by engineer. */ "strike" = "staking"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Deze chat is beveiligd met end-to-end codering."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Deze chat wordt beschermd door quantum bestendige end-to-end codering."; + /* notification title */ "this contact" = "dit contact"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Upgrade en open chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Upload mislukt"; + /* server test step */ "Upload file" = "Upload bestand"; +/* No comment provided by engineer. */ +"Uploading archive" = "Archief uploaden"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Gebruik .onion-hosts"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat servers gebruiken?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Gebruik de app tijdens het gesprek."; + /* No comment provided by engineer. */ "User profile" = "Gebruikers profiel"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Controleer verbindingen"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Controleer het wachtwoord van de database"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Controleer het wachtwoord"; + /* No comment provided by engineer. */ "Verify security code" = "Controleer de beveiligingscode"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "wil met je in contact komen!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Waarschuwing: u kunt sommige gegevens verliezen!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Welkomst bericht"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Welkomstbericht is te lang"; + /* No comment provided by engineer. */ "What's new" = "Wat is er nieuw"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Jij"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "U **mag** niet dezelfde database op twee apparaten gebruiken."; + /* No comment provided by engineer. */ "You accepted connection" = "Je hebt de verbinding geaccepteerd"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "U kunt ze later inschakelen via de privacy- en beveiligingsinstellingen van de app."; +/* No comment provided by engineer. */ +"You can give another try." = "Je kunt het nog een keer proberen."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "U kunt een gebruikers profiel verbergen of dempen - veeg het naar rechts."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index bbacb49fb3..5283ba5ad0 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, sprawdzaj wiadomości okresowo w tle (zależy jak często korzystasz z aplikacji)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "*Uwaga*: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Uwaga**: NIE będziesz w stanie odzyskać lub zmienić hasła, jeśli je stracisz."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Uwaga**: Natychmiastowe powiadomienia push wymagają hasła zapisanego w Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Ostrzeżenie**: archiwum zostanie usunięte."; + /* No comment provided by engineer. */ "*bold*" = "\\*pogrubiony*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ połączony"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ pobrane"; + /* notification title */ "%@ is connected!" = "%@ jest połączony!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ serwery"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ wgrane"; + /* notification title */ "%@ wants to connect!" = "%@ chce się połączyć!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "administrator"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Administratorzy mogą blokować członka dla wszystkich."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Administratorzy mogą tworzyć linki do dołączania do grup."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Wszystkie Twoje kontakty pozostaną połączone. Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP."; + /* No comment provided by engineer. */ "Allow" = "Pozwól"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Kompilacja aplikacji: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migracja danych aplikacji"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Aplikacja szyfruje nowe lokalne pliki (bez filmów)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Wygląd"; +/* No comment provided by engineer. */ +"Apply" = "Zastosuj"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiwizuj i prześlij"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archiwizowanie bazy danych"; + /* No comment provided by engineer. */ "Attach" = "Dołącz"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Anuluj"; +/* No comment provided by engineer. */ +"Cancel migration" = "Anuluj migrację"; + /* feature offered item */ "cancelled %@" = "anulowany %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Czat zmigrowany!"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferencje czatu"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chiński i hiszpański interfejs"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Wybierz _Zmigruj z innego urządzenia_ na nowym urządzeniu i zeskanuj kod QR."; + /* No comment provided by engineer. */ "Choose file" = "Wybierz plik"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Potwierdź aktualizacje bazy danych"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Potwierdź ustawienia sieciowe"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Potwierdź nowe hasło…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Potwierdź hasło"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Potwierdź wgranie"; + /* server test step */ "Connect" = "Połącz"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Utworzony w dniu %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Tworzenie linku archiwum"; + /* No comment provided by engineer. */ "Creating link…" = "Tworzenie linku…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Usuń bazę danych"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Usuń bazę danych z tego urządzenia"; + /* server test step */ "Delete file" = "Usuń plik"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Obniż wersję i otwórz czat"; +/* No comment provided by engineer. */ +"Download failed" = "Pobieranie nie udane"; + /* server test step */ "Download file" = "Pobierz plik"; +/* No comment provided by engineer. */ +"Downloading archive" = "Pobieranie archiwum"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Pobieranie szczegółów linku"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Zduplikowana wyświetlana nazwa!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Włącz dla wszystkich"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Włącz w czatach bezpośrednich (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Włączyć natychmiastowe powiadomienia?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Wprowadź Pin"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Wprowadź hasło"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Wprowadź hasło…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Błąd dodawania członka(ów)"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Błąd pozwalania kontaktowi na szyfrowanie PQ"; + /* No comment provided by engineer. */ "Error changing address" = "Błąd zmiany adresu"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Błąd usuwania profilu użytkownika"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Błąd pobierania archiwum"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Błąd włączania potwierdzeń dostawy!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Błąd zapisu hasła do pęku kluczy"; +/* when migrating */ +"Error saving settings" = "Błąd zapisywania ustawień"; + /* No comment provided by engineer. */ "Error saving user password" = "Błąd zapisu hasła użytkownika"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Błąd aktualizacji prywatności użytkownika"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Błąd wgrywania archiwum"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Błąd weryfikowania hasła:"; + /* No comment provided by engineer. */ "Error: " = "Błąd: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Wyeksportowane archiwum bazy danych."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Wyeksportowany plik nie istnieje"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Eksportowanie archiwum bazy danych…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtruj nieprzeczytane i ulubione czaty."; +/* No comment provided by engineer. */ +"Finalize migration" = "Dokończ migrację"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Dokończ migrację na innym urządzeniu."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "W końcu je mamy! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Jak korzystać z Twoich serwerów"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Węgierski interfejs"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Serwery ICE (po jednym na linię)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importuj bazę danych"; +/* No comment provided by engineer. */ +"Import failed" = "Import nie udał się"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importowanie archiwum"; + /* No comment provided by engineer. */ "Improved message delivery" = "Ulepszona dostawa wiadomości"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Ulepszona konfiguracja serwera"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Aby konturować, czat musi zostać zatrzymany."; + /* No comment provided by engineer. */ "In reply to" = "W odpowiedzi na"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Nieprawidłowy link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Nieprawidłowe potwierdzenie migracji"; + /* No comment provided by engineer. */ "Invalid name!" = "Nieprawidłowa nazwa!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Tekst wiadomości"; +/* No comment provided by engineer. */ +"Message too large" = "Wiadomość jest zbyt duża"; + /* No comment provided by engineer. */ "Messages" = "Wiadomości"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Wiadomości od %@ zostaną pokazane!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu."; + +/* No comment provided by engineer. */ +"Migrate device" = "Zmigruj urządzenie"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Zmigruj z innego urządzenia"; + +/* No comment provided by engineer. */ +"Migrate here" = "Zmigruj tutaj"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Zmigruj do innego urządzenia"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Zmigruj do innego urządzenia przez kod QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrowanie"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migrowanie archiwum bazy danych…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migracja zakończona"; + /* No comment provided by engineer. */ "Migration error:" = "Błąd migracji:"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Grupa otwarta"; +/* authentication reason */ +"Open migration to another device" = "Otwórz migrację na innym urządzeniu"; + /* No comment provided by engineer. */ "Open Settings" = "Otwórz Ustawienia"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Otwieranie aplikacji…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Lub wklej link archiwum"; + /* No comment provided by engineer. */ "Or scan QR code" = "Lub zeskanuj kod QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Lub bezpiecznie udostępnij ten link pliku"; + /* No comment provided by engineer. */ "Or show this code" = "Lub pokaż ten kod"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Stały błąd odszyfrowania"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Połączenia obraz-w-obrazie"; + /* No comment provided by engineer. */ "PING count" = "Liczba PINGÓW"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Proszę sprawdzić preferencje Twoje i Twojego kontaktu."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Proszę skontaktować się z deweloperami.\nBłąd: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Postkwantowe E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Powiadomienia push"; +/* No comment provided by engineer. */ +"Push server" = "Serwer Push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "kwantowo odporne szyfrowanie e2e"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Kwantowo odporne szyfrowanie"; + /* No comment provided by engineer. */ "Rate the app" = "Oceń aplikację"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Powtórzyć prośbę połączenia?"; +/* No comment provided by engineer. */ +"Repeat download" = "Powtórz pobieranie"; + +/* No comment provided by engineer. */ +"Repeat import" = "Powtórz importowanie"; + /* No comment provided by engineer. */ "Repeat join request?" = "Powtórzyć prośbę dołączenia?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Powtórz wgrywanie"; + /* chat item action */ "Reply" = "Odpowiedz"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Uruchom czat"; +/* No comment provided by engineer. */ +"Safer groups" = "Bezpieczniejsze grupy"; + /* chat item action */ "Save" = "Zapisz"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Ustaw pin"; +/* No comment provided by engineer. */ +"Set passphrase" = "Ustaw hasło"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Ustaw hasło do eksportu"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Pokaż podgląd"; +/* No comment provided by engineer. */ +"Show QR code" = "Pokaż kod QR"; + /* No comment provided by engineer. */ "Show:" = "Pokaż:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Ktoś"; +/* chat item text */ +"standard end-to-end encryption" = "standardowe szyfrowanie end-to-end"; + /* No comment provided by engineer. */ "Start chat" = "Rozpocznij czat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Zatrzymaj"; +/* No comment provided by engineer. */ +"Stop chat" = "Zatrzymaj czat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Zatrzymaj czat, aby umożliwić działania na bazie danych"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Zatrzymaj SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Zatrzymywanie czatu"; + /* No comment provided by engineer. */ "strike" = "strajk"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Ten czat jest chroniony przez szyfrowanie end-to-end."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end."; + /* notification title */ "this contact" = "ten kontakt"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Zaktualizuj i otwórz czat"; +/* No comment provided by engineer. */ +"Upload failed" = "Wgrywanie nie udane"; + /* server test step */ "Upload file" = "Prześlij plik"; +/* No comment provided by engineer. */ +"Uploading archive" = "Wgrywanie archiwum"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Użyj hostów .onion"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Użyć serwerów SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Używaj aplikacji podczas połączenia."; + /* No comment provided by engineer. */ "User profile" = "Profil użytkownika"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Zweryfikuj połączenia"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Zweryfikuj hasło bazy danych"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Zweryfikuj hasło"; + /* No comment provided by engineer. */ "Verify security code" = "Weryfikuj kod bezpieczeństwa"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "chce się z Tobą połączyć!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Uwaga: możesz stracić niektóre dane!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Wiadomość powitalna"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Wiadomość powitalna jest zbyt długa"; + /* No comment provided by engineer. */ "What's new" = "Co nowego"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Ty"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "**Nie możesz** używać tej samej bazy na dwóch urządzeniach."; + /* No comment provided by engineer. */ "You accepted connection" = "Zaakceptowałeś połączenie"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Możesz je włączyć później w ustawieniach Prywatności i Bezpieczeństwa aplikacji."; +/* No comment provided by engineer. */ +"You can give another try." = "Możesz spróbować ponownie."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Możesz ukryć lub wyciszyć profil użytkownika - przesuń palcem w prawo."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index da23c04e10..e82ab7a07f 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Внимание**: архив будет удален."; + /* No comment provided by engineer. */ "*bold*" = "\\*жирный*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ соединен(а)"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ загружено"; + /* notification title */ "%@ is connected!" = "Установлено соединение с %@!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ серверы"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ загружено"; + /* notification title */ "%@ wants to connect!" = "%@ хочет соединиться!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "админ"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Админы могут заблокировать члена группы."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Админы могут создать ссылки для вступления в группу."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы."; + /* No comment provided by engineer. */ "Allow" = "Разрешить"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Сборка приложения: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Миграция данных"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Приложение шифрует новые локальные файлы (кроме видео)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Интерфейс"; +/* No comment provided by engineer. */ +"Apply" = "Применить"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Архивировать и загрузить"; + +/* No comment provided by engineer. */ +"Archiving database" = "Подготовка архива"; + /* No comment provided by engineer. */ "Attach" = "Прикрепить"; @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Отменить"; +/* No comment provided by engineer. */ +"Cancel migration" = "Отменить миграцию"; + /* feature offered item */ "cancelled %@" = "отменил(a) %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Чат мигрирован!"; + /* No comment provided by engineer. */ "Chat preferences" = "Предпочтения"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Китайский и Испанский интерфейс"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код."; + /* No comment provided by engineer. */ "Choose file" = "Выбрать файл"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Подтвердить обновление базы данных"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Подтвердите настройки сети"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Подтвердите новый пароль…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Подтвердить пароль"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Подтвердите, что Вы помните пароль базы данных для ее миграции."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Подтвердить загрузку"; + /* server test step */ "Connect" = "Соединиться"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Дата создания %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Создание ссылки на архив"; + /* No comment provided by engineer. */ "Creating link…" = "Создаётся ссылка…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Удалить данные чата"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Удалить базу данных с этого устройства"; + /* server test step */ "Delete file" = "Удалить файл"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Откатить версию и открыть чат"; +/* No comment provided by engineer. */ +"Download failed" = "Ошибка загрузки"; + /* server test step */ "Download file" = "Загрузка файла"; +/* No comment provided by engineer. */ +"Downloading archive" = "Загрузка архива"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Загрузка ссылки архива"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Имя профиля уже используется!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Включите для контактов (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Включить мгновенные уведомления?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Введите Код"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Введите пароль"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Введите пароль…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Ошибка разрешения квантово-устойчивого шифрования"; + /* No comment provided by engineer. */ "Error changing address" = "Ошибка при изменении адреса"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Ошибка удаления профиля пользователя"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Ошибка загрузки архива"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Ошибка при включении отчётов о доставке!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Ошибка сохранения пароля в Keychain"; +/* when migrating */ +"Error saving settings" = "Ошибка сохранения настроек"; + /* No comment provided by engineer. */ "Error saving user password" = "Ошибка при сохранении пароля пользователя"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Ошибка при обновлении конфиденциальности"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Ошибка загрузки архива"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Ошибка подтверждения пароля:"; + /* No comment provided by engineer. */ "Error: " = "Ошибка: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Архив чата экспортирован."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Экспортированный файл не существует"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Архив чата экспортируется…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Фильтровать непрочитанные и избранные чаты."; +/* No comment provided by engineer. */ +"Finalize migration" = "Завершить миграцию"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Завершите миграцию на другом устройстве."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Наконец-то, мы их добавили! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Как использовать серверы"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Венгерский интерфейс"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE серверы (один на строке)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Импорт архива чата"; +/* No comment provided by engineer. */ +"Import failed" = "Ошибка импорта"; + +/* No comment provided by engineer. */ +"Importing archive" = "Импорт архива"; + /* No comment provided by engineer. */ "Improved message delivery" = "Улучшенная доставка сообщений"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Улучшенная конфигурация серверов"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Чтобы продолжить, чат должен быть остановлен."; + /* No comment provided by engineer. */ "In reply to" = "В ответ на"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ошибка ссылки"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Ошибка подтверждения миграции"; + /* No comment provided by engineer. */ "Invalid name!" = "Неверное имя!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Текст сообщения"; +/* No comment provided by engineer. */ +"Message too large" = "Сообщение слишком большое"; + /* No comment provided by engineer. */ "Messages" = "Сообщения"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Сообщения от %@ будут показаны!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; + +/* No comment provided by engineer. */ +"Migrate device" = "Мигрировать устройство"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Миграция с другого устройства"; + +/* No comment provided by engineer. */ +"Migrate here" = "Мигрировать сюда"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Мигрировать на другое устройство"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Мигрируйте на другое устройство через QR код."; + +/* No comment provided by engineer. */ +"Migrating" = "Миграция"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Данные чата перемещаются…"; +/* No comment provided by engineer. */ +"Migration complete" = "Миграция завершена"; + /* No comment provided by engineer. */ "Migration error:" = "Ошибка при перемещении данных:"; @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Открыть группу"; +/* authentication reason */ +"Open migration to another device" = "Открытие миграции на другое устройство"; + /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Или вставьте ссылку архива"; + /* No comment provided by engineer. */ "Or scan QR code" = "Или отсканируйте QR код"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Или передайте эту ссылку"; + /* No comment provided by engineer. */ "Or show this code" = "Или покажите этот код"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Ошибка расшифровки"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Звонки с картинкой-в-картинке"; + /* No comment provided by engineer. */ "PING count" = "Количество PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Проверьте предпочтения Вашего контакта."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Пожалуйста, подтвердите, что настройки сети верны для этого устройства."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Пожалуйста, сообщите разработчикам.\nОшибка: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Возможно, хэш сертификата в адресе сервера неверный"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Квантово-устойчивое E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Сохранить последний черновик, вместе с вложениями."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Доставка уведомлений"; +/* No comment provided by engineer. */ +"Push server" = "Сервер уведомлений"; + +/* chat item text */ +"quantum resistant e2e encryption" = "квантово-устойчивое e2e шифрование"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Квантово-устойчивое шифрование"; + /* No comment provided by engineer. */ "Rate the app" = "Оценить приложение"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Повторить запрос на соединение?"; +/* No comment provided by engineer. */ +"Repeat download" = "Повторить загрузку"; + +/* No comment provided by engineer. */ +"Repeat import" = "Повторить импорт"; + /* No comment provided by engineer. */ "Repeat join request?" = "Повторить запрос на вступление?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Повторить загрузку"; + /* chat item action */ "Reply" = "Ответить"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Запустить chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Более безопасные группы"; + /* chat item action */ "Save" = "Сохранить"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Установить код доступа"; +/* No comment provided by engineer. */ +"Set passphrase" = "Установить пароль"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Установите пароль"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Показывать уведомления"; +/* No comment provided by engineer. */ +"Show QR code" = "Показать QR код"; + /* No comment provided by engineer. */ "Show:" = "Показать:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Контакт"; +/* chat item text */ +"standard end-to-end encryption" = "стандартное end-to-end шифрование"; + /* No comment provided by engineer. */ "Start chat" = "Запустить чат"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Остановить"; +/* No comment provided by engineer. */ +"Stop chat" = "Остановить чат"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Остановите чат, чтобы разблокировать операции с архивом чата"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Остановить SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Остановка чата"; + /* No comment provided by engineer. */ "strike" = "зачеркнуть"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Чат защищен end-to-end шифрованием."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Чат защищен квантово-устойчивым end-to-end шифрованием."; + /* notification title */ "this contact" = "этот контакт"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Обновить и открыть чат"; +/* No comment provided by engineer. */ +"Upload failed" = "Ошибка загрузки"; + /* server test step */ "Upload file" = "Загрузка файла"; +/* No comment provided by engineer. */ +"Uploading archive" = "Загрузка архива"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Использовать .onion хосты"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Используйте приложение во время звонка."; + /* No comment provided by engineer. */ "User profile" = "Профиль чата"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Проверять соединения"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Проверка пароля базы данных"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Проверить пароль"; + /* No comment provided by engineer. */ "Verify security code" = "Подтвердить код безопасности"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "хочет соединиться с Вами!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Предупреждение: Вы можете потерять какие то данные!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Приветственное сообщение"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Приветственное сообщение слишком длинное"; + /* No comment provided by engineer. */ "What's new" = "Новые функции"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Вы"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Вы **не должны** использовать одну и ту же базу данных на двух устройствах."; + /* No comment provided by engineer. */ "You accepted connection" = "Вы приняли приглашение соединиться"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Вы можете включить их позже в настройках Конфиденциальности."; +/* No comment provided by engineer. */ +"You can give another try." = "Вы можете попробовать еще раз."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Вы можете скрыть профиль или выключить уведомления - потяните его вправо."; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 8f7eea47c5..2e866ebe08 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Lütfen dikkat**: Aynı veritabanını iki cihazda kullanmak, güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Uyarı**: arşiv silinecektir."; + /* No comment provided by engineer. */ "*bold*" = "\\*kalın*"; @@ -131,11 +137,14 @@ "%@ and %@ connected" = "%@ ve %@ bağlandı"; /* copied message info, at Fehler beim Akzeptieren der Kontaktanfrage Der Absender hat möglicherweise die Verbindungsanfrage gelöscht. Fehler beim Löschen des Kontakts @@ -262,7 +262,7 @@ Video Danke, dass Sie SimpleX Chat installiert haben! - mit SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und Updates zu erhalten.]]> + mit den SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und aktuelle Informationen zu erhalten.]]> Um einen neuen Chat zu starten Schaltfläche antippen Danach die gewünschte Aktion auswählen: @@ -1214,7 +1214,7 @@ Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können. SimpleX-Adresse erstellen Mit Kontakten teilen - Ihre Kontakte bleiben verbunden. + Ihre Kontakte bleiben weiterhin verbunden. Automatisch akzeptieren Geben Sie eine Begrüßungsmeldung ein … (optional) Freunde einladen @@ -1720,4 +1720,81 @@ Videoanruf Fehler beim Öffnen des Browsers Für Anrufe ist ein Default-Webbrowser erforderlich. Bitte konfigurieren Sie einen Default-Browser für das System und teilen Sie den Entwicklern mehr Informationen dazu mit. + Chat wurde migriert! + Archiv-Link erzeugen + Von einem anderen Gerät migrieren und scannen Sie den QR-Code.]]> + Datenbank auf diesem Gerät löschen + App-Daten-Migration + Administratoren können ein Gruppenmitglied für Alle blockieren. + Link-Details werden heruntergeladen + Archiv wird heruntergeladen + Anwenden + Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen. + Archivieren und Hochladen + Warnung: Das Archiv wird gelöscht.]]> + Überprüfen Sie Ihre Internet-Verbindung und probieren Sie es nochmals + Datenbank wird archiviert + Bitte beachten Sie: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen.]]> + Migration abbrechen + Bestätigen Sie die Netzwerkeinstellungen + Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern. + Hochladen bestätigen + Herunterladen fehlgeschlagen + Ende-zu-Ende-Verschlüsselung mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]> + Quantum-resistente E2E-Verschlüsselung mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]> + Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt. + Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt. + Migrationsansicht öffnen + Passwort festlegen + Quantum-resistente E2E-Verschlüsselung + Oder fügen Sie den Archiv-Link ein + Archiv-Link einfügen + Ungültiger Link + Migrieren + %s heruntergeladen + Die Migration auf dem anderen Gerät wird abgeschlossen. + Fehler beim Herunterladen des Archivs + Die Migration wird abgeschlossen + Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen + Chat starten + Kann in direkten Chats aktiviert werden (BETA)! + Quantum-resistente Verschlüsselung + Daten können über einen QR-Code auf ein anderes Gerät migriert werden. + Bild-in-Bild-Anrufe + Sicherere Gruppen + Die App kann während eines Anrufs genutzt werden. + Hierher migrieren + Das Herunterladen wird vorbereitet + Herunterladen wiederholen + Sie können es nochmal probieren. + Passwort eingeben + Archiv wird importiert + Import wiederholen + Die Datei wurde gelöscht oder der Link ist ungültig + Gerät migrieren + Auf ein anderes Gerät migrieren + Fehler beim Exportieren der Chat-Datenbank + Fehler beim Abspeichern der Einstellungen + Die exportierte Datei ist nicht vorhanden + Fehler beim Löschen der Datenbank + Fehler beim Hochladen des Archivs + Das Hochladen wird vorbereitet + Um fortzufahren, sollte der Chat beendet werden. + %s hochgeladen + Archiv wird hochgeladen + Chat wird beendet + Oder teilen Sie diesen Datei-Link sicher + Hochladen wiederholen + Hochladen fehlgeschlagen + Überprüfen Sie das Datenbank-Passwort + Sie können es nochmal probieren. + Fehler bei der Überprüfung des Passworts: + Überprüfen Sie das Passwort + Von einem anderen Gerät migrieren + Import ist fehlgeschlagen + Migration abgeschlossen + Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind. + Standard-Ende-zu-Ende-Verschlüsselung + nicht auf zwei Geräten nutzen.]]> + Fehler beim Anzeigen der Benachrichtigung. Bitte kontaktieren Sie die Entwickler. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 8531bce63f..df52456401 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -1640,4 +1640,80 @@ Videollamada Error al abrir el navegador Para llamadas se requiere el navegador web predeterminado. Por favor, configura el navegador predeterminado en el sistema y comparte más información con los desarrolladores. + Archivando base de datos + Todos tus contactos, conversaciones y archivos serán cifrados, divididos y subidos de forma segura a los servidores XFTP configurados. + Los administradores pueden bloquear un miembro para el resto + Chat migrado! + Migración de los datos de la app + Aplicar + Archivar y subir + Cancelar migración + Este chat está protegido por cifrado de extremo a extremo. + Migrar desde otro dispositivo + cifrado e2e resistente a tecnologías cuánticas + cifrado estándar de extremo a extremo + %s descargado + O comparta de forma segura este enlace de archivo + Migración completada + Empezar chat + Advertencia: el inicio del chat en varios dispositivos no es compatible y provocará fallos en la entrega de mensajes. + Verificar la frase de contraseña + Verificar la contraseña de la base de datos + ¡Habilitar en chats directos (BETA)! + Usar la aplicación durante la llamada. + Grupos más seguros + Descargando detalles del enlace + Enlace inválido + Migrando + Pegar enlace de archivo + Preparando descarga + Repetir descarga + Puedes intentarlo de nuevo. + Introducir frase de contraseña + Error al descargar el archivo + El archivo se ha eliminado o el enlace no es válido + Finalizar la migración en otro dispostivo. + Error de importación + Importando archivo + Repetir importación + Migrar dispositivo + Por favor confirme que la configuración de red es correcta para este dispositivo. + Error al borrar la base de datos + Error al subir el archivo + Deteniendo chat + Repetir la carga + %s subidos + Error de subida + Subiendo archivo + Puedes intentarlo de nuevo. + Creando enlace de archivo + Borrar la base de datos de este dispositivo + Finalizar la migración + Atención: el archivo se eliminará.]]> + Compruebe su conexión a Internet y vuelva a intentarlo + Confirme que recuerda la contraseña de la base de datos para migrarla. + Error al verificar la contraseña: + Ten en cuenta: usar la misma base de datos en dos dispositivos romperá el descifrado de mensajes de tus conexiones, como protección de seguridad.]]> + Migrar desde otro dispositivo en el nuevo dispositivo y escanea el código QR.]]> + Confirmar la configuración de red + Confirmar carga + Descarga fallida + Descargando archivo + Error al exportar la base de datos de chats + Error al guardar los ajustes + El archivo exportado no existe + Para continuar, el chat debe detenerse. + cifrado de extremo a extremo con perfecta confidencialidad, repudio y recuperación tras ataques.]]> + cifrado de extremo a extremo resistente a computación cuántica con perfecta confidencialidad, repudio y recuperación tras ataques.]]> + Migrar aquí + Migrar hacia otro dispositivo + Migrar hacia otro dispositivo mediante código QR. + Abrir pantalla de migración + O pegar enlace del archivo + Llamadas picture-in-picture + Preparando subida + Cifrado resistente a tecnologías cuánticas + Establecer frase de contraseña + Este chat está protegido por un cifrado de extremo a extremo resistente a tecnología cuántica. + No debes utilizar la misma base de datos en dos dispositivos.]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index b0ebf891e9..ead3957aa5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -1639,4 +1639,80 @@ Appel audio Le navigateur web par défaut est requis pour les appels. Veuillez configurer le navigateur par défaut dans le système et partager plus d\'informations avec les développeurs. Erreur lors de l\'ouverture du navigateur + Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer. + Transfert des données de l\'application + Les admins peuvent bloquer un membre pour tous. + Le fichier exporté n\'existe pas + Erreur lors de l\'envoi de l\'archive + Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés. + Appliquer + Archiver et transférer + Archivage de la base de données + Avertissement : l\'archive sera supprimée.]]> + Remarque : l\'utilisation d\'une même base de données sur deux appareils interrompra le déchiffrement des messages provenant de vos connexions, par mesure de sécurité.]]> + Annuler le transfert + Messagerie transférée ! + Vérifiez votre connexion internet et réessayez + Échec du téléchargement + Transférer depuis un autre appareil sur le nouvel appareil et scanner le code QR.]]> + Confirmer les paramètres réseau + Confirmer la transmission + Création d\'un lien d\'archive + Supprimer la base de données de cet appareil + Téléchargement de l\'archive + Téléchargement des détails du lien + Activé dans les conversations directes (BETA) ! + Entrer la phrase secrète + Erreur lors de la suppression de la base de données + Erreur lors de l\'exportation de la base de données des chats + Erreur lors du téléchargement de l\'archive + Erreur lors de l\'enregistrement des paramètres + Erreur lors de la vérification de la phrase secrète : + chiffrement de bout en bout avec une confidentialité persistante, une répudiation et une récupération en cas d\'effraction.]]> + chiffrement e2e résistant post-quantique avec une confidentialité persistante, une répudiation et une récupération en cas d\'effraction.]]> + Cette discussion est protégée par un chiffrement de bout en bout. + Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique. + Accéder à l\'écran de transfert + Transférer depuis un autre appareil + Définir une phrase secrète + chiffrement e2e résistant post-quantique + Échec de l\'envoi + Vous pouvez faire un nouvel essai. + Répéter l\'envoi + Transférer vers un autre appareil via un code QR. + Chiffrement résistant post-quantique + Appels picture-in-picture + Groupes plus sûrs + Utiliser l\'application pendant l\'appel. + Préparation du téléchargement + %s téléchargés + Importation de l\'archive + Répéter l\'importation + Finalisez le transfert sur l\'autre appareil. + Transférer l\'appareil + Transférer vers un autre appareil + Préparation de l\'envoi + Pour continuer, le chat doit être interrompu. + Arrêt du chat + %s envoyé + Envoi de l\'archive + Finaliser le transfert + Transfert terminé + Démarrer le chat + ne devez pas utiliser la même base de données sur deux appareils.]]> + Vérifier la phrase secrète de la base de données + Répéter le téléchargement + Veuillez confirmer que les paramètres réseau de cet appareil sont corrects. + chiffrement de bout en bout standard + Le fichier a été supprimé ou le lien est invalide + Échec de l\'importation + Lien invalide + Transférer ici + Transfert + Ou coller le lien de l\'archive + Ou partagez en toute sécurité le lien de ce fichier + Coller le lien de l\'archive + Vérifier la phrase secrète + Attention : démarrer une session de chat sur plusieurs appareils n\'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages + Vous pouvez faire un nouvel essai. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index a323741f5c..0c244c8806 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -1,6 +1,6 @@ - %1$d üzenet visszafejtése sikertelen + %1$d üzenet visszafejtése sikertelen. %1$d üzenet kihagyva. %1$d kihagyott üzenet %1$s TAG @@ -29,10 +29,10 @@ Kapcsolatfelvétel elfogadása? Elfogadás Elfogadás - Azonosító hozzáadása a profilhoz, hogy az ismerősök megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősők számára. + Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára. További kiemelés hiba a hívásban - Csoporttagok blokkolása + Csoporttagok letiltása Hitelesítés Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik. megszakítva %s @@ -50,17 +50,17 @@ Alkalmazásadatok biztonsági mentése Az adatbázis inicializálása sikertelen Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. - A csevegési profil által (alap beállítás), vagy kapcsolat által (BÉTA). + A csevegési profil által (alap beállítás), vagy a kapcsolat által (BÉTA). Egy új véletlenszerű profil lesz megosztva. - Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. Az alkalmazás build száma: %s Hang-/videóhívások Speciális hálózati beállítások - Hangüzenetek küldésének engedélyezése ismerősök számára + Hangüzenetek küldésének engedélyezése az ismerősei számára. Hang- és videóhívások Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével). Hívás fogadása - Eltűnő üzenetek engedélyezése ismerősök számára. + Eltűnő üzenetek engedélyezése az ismerősei számára. Kapcsolódás folyamatban! Nem lehet fogadni a fájlt Hitelesítés elérhetetlen @@ -79,12 +79,12 @@ Csatlakozás folyamatban! Automatikus elfogadás A háttérszolgáltatás mindig fut - az értesítések azonnal megjelennek, amint üzenetek vannak. - Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése. (24 óra) + Az elküldött üzenetek visszafordíthatatlan törlése engedélyezve van. (24 óra) Mindkét fél küldhet hangüzeneteket. Téves üzenet ID - Ismerősök általi üzenetreakciók hozzáadásának engedélyezése. - Hangüzenetek küldésének engedélyezése. - Üzenetreakciók engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + Ismerősök általi üzenetreakciók küldésének engedélyezése. + A hangüzenetek küldése engedélyezve van. + Üzenetreakciók engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. Vissza Kikapcsolható a beállításokban – az értesítések továbbra is megjelenítésre kerülnek amíg az alkalmazás fut.]]> Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. @@ -93,23 +93,23 @@ Ismerősök meghívása le van tiltva! téves üzenet ID Ismerős jelölések automatikus elfogadása - Figyelem: NEM fogja tudni helyreállítani vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> + Figyelem: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos Hozzáadás egy másik eszközhöz - Üzenetreakciók engedélyezése. + Az üzenetreakciók küldése engedélyezve van. Fájl előnézet megszakítása Minden csoporttag csatlakoztatva marad. Több akkumulátort használ! Háttérszolgáltatás mindig fut - az értesítések megjelennek, amint az üzenetek elérhetővé válnak.]]> - Blokkolás + Letiltás admin Fénykép előnézet megszakítása A jelkód megadása után minden adat törlésre kerül. Felkérték a videó fogadására - Tag blokkolása + Tag letiltása Még néhány dolog Hitelesítés megszakítva - Fájlok és médiatartalom küldésének engedélyezése. + A fájlok- és a médiatartalom küldése engedélyezve van. Minden csevegés és üzenet törlésre kerül - ez nem vonható vissza! hanghívás félkövér @@ -122,20 +122,20 @@ Engedélyezés Minden ismerős csatlakoztatva marad. Élő csevegési üzenet megszakítása - Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. (24 óra) + Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások téves üzenet hash Mindig bekapcsolva - Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását. + Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását. Minden alkalmazásadat törölve. Legjobb akkumulátoridő. Csak akkor kap értesítést, ha az alkalmazás fut (NINCS háttérszolgáltatás).]]> Megjelenés Az akkumulátor optimalizálása aktív, mely kikapcsolja a háttérszolgáltatást és az új üzenetek rendszeres kérését. A beállításokon keresztül újra engedélyezhetők. - Tag blokkolása? + Tag letiltása? %1$s hívása befejeződött - Jó akkumulátoridő. A háttérszolgáltatás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról vagy a sürgős üzeneteketről marad le.]]> + Jó akkumulátoridő. A háttérszolgáltatás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról, vagy a sürgős üzenetekről marad le.]]> szerző - Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése ismerősök számára. (24 óra) + Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése az ismerősei számára. (24 óra) Megszakítás Az alkalmazás csak akkor tud értesítéseket fogadni amikor fut, háttérszolgáltatás nem indul el Jobb üzenetek @@ -150,25 +150,25 @@ A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó mentése végett hívás folyamatban Fotók automatikus elfogadása - Hívások engedélyezése ismerősök számára. + Hívások engedélyezése az ismerősei számára. ALKALMAZÁS IKON Kiszolgáló hozzáadása QR-kód beolvasásával. - Eltűnő üzenetek küldésének engedélyezése. - Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + Az eltűnő üzenetek küldése engedélyezve van. + Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. Hang kikapcsolva - Közvetlen üzenetek küldésének engedélyezése tagok részére. + A közvetlen üzenetek küldése a tagok számára engedélyezve van. Alkalmazás Hívás folyamatban - Mindkét fél is hozzáadhat üzenetreakciókat. + Mindkét fél küldhet üzenetreakciókat. Mindkét fél tud hívásokat indítani. Hitelesítés sikertelen Minden %s által írt új üzenet elrejtésre kerül! Alkalmazás verzió: v%s - Hívások engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + Hívások engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. Kiszolgáló hozzáadása… Hang bekapcsolva hanghívás (nem e2e titkosított) - blokkolva + letiltva Adatbázis jelmondat megváltoztatása? kapcsolódva Jelkód megváltoztatása @@ -205,7 +205,7 @@ Kapcsolódás egy hivatkozás / QR-kód által Kapcsolódási hiba (AUTH) Ismerős neve - Kapcsolódás ismerős azonosítója által? + Kapcsolódik ehhez az ismerőshöz? Azonosító létrehozása Másolás Folytatás @@ -236,7 +236,7 @@ Kapcsolódási hiba Az ismerős még nem csatlakozott! - kapcsolódás könyvtár szolgáltatáshoz (BÉTA)! -\n- kézbesítési igazolások (20 tagig). +\n- kézbesítési jelentések (20 tagig). \n- gyorsabb és stabilabb Hozzájárulás csatlakozás (bemutatkozás meghívó) @@ -245,7 +245,7 @@ Csoporttag üzenet törlése? A csevegés fut Egyszer használatos meghívó hivatkozás létrehozása - Hivatkozás törlése + Törlés Új üzenetek ellenőrzése 10 percenként, legfeljebb 1 percen keresztül. Adatbázis törlése Csoport létrehozása @@ -342,15 +342,15 @@ Ismerős törlése? Kiürítés Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel. - Biztonsági kódok összehasonlítása ismerősökkel. + Biztonsági kódok összehasonlítása az ismerőseiével. Fájl összehasonlítás Csevegések Üzenet törlése? Függő kapcsolatfelvételi kérések törlése? Adatbázis titkosítva! - Csevegés kiürítése? + Üzenetek törlése? Visszatérés a korábbi adatbázis verzióra - Csevegés kiürítése + Üzenetek törlése Adatbázis titkosítási jelmondat frissítve lesz. Kapcsolódás automatikusan Adatbázis hiba @@ -371,7 +371,7 @@ %d hét Számítógép azonosítója %dmp - Kézbesítési igazolások! + Kézbesítési jelentések! Eszközhitelesítés nem engedélyezett.A SimpleX zárolás bekapcsolható a Beállításokon keresztül, miután az eszköz hitelesítés engedélyezésre került. Titkosítás visszafejtési hiba Eltűnik ekkor: %s @@ -418,7 +418,7 @@ Duplikált megjelenítési név! Letiltás (felülírások megtartásával) Adatbázis fejlesztése - %d üzenet blokkolva + %d üzenet letiltva Eltűnik ekkor %d hét engedélyezve az ön számára @@ -429,7 +429,7 @@ %d másodperc Minden fájl törlése Az adatbázis titkosításra kerül. - Adatbázis jelmondat és exportálás + Adatbázis jelmondat és -exportálás Az adatbázis titkosításra kerül és a jelmondat eltárolásra a Keystore-ban. Automatikus üzenet törlés engedélyezése? Törlés @@ -463,7 +463,7 @@ Adatbázis jelmondat szükséges a csevegés megnyitásához. %dnap Engedélyezés mindenki részére - Kézbesítési igazolások kikapcsolva! + Kézbesítési jelentések kikapcsolva! Kibontás Hiba az üzenet küldésekor Jelkód megadása @@ -547,11 +547,11 @@ Üdvözlő üzenetet megadása… Titkosított adatbázis Jelszó megadása a keresőben - A fájl akkor érkezik meg, amikor ismerőse befejezte annak feltöltést. + A fájl akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését. Fájl letöltése Csevegés betöltése sikertelen Kiszolgáló megadása kézzel - A fájl akkor érkezik meg, amint ismerőse online lesz, várjon, vagy ellenőrizze később! + A fájl akkor érkezik meg, amikor az ismerőse online lesz, várjon, vagy ellenőrizze később! Hiba a csoport hivatkozásának létrehozásakor A Galériából Engedélyezés (csoport felülírások megtartásával) @@ -653,7 +653,7 @@ Érvénytelen csevegés óra Inkognitó - Hogyan használja + Használati útmutató Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között. Javított kiszolgáló konfiguráció Előzmények @@ -671,12 +671,12 @@ Elrejt: Hiba az ismerőssel történő kapcsolat létrehozásában ICE-kiszolgálók (soronként egy) - beolvashatja a QR-kódot a videohívásban, vagy ismerőse megoszthat egy meghívó hivatkozást.]]> + beolvashatja a QR-kódot a videohívásban, vagy az ismerőse megoszthat egy meghívó hivatkozást.]]> Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat visszafordíthatatlanul törlődik! Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást. mutassa meg a QR-kódot a videohívásban, vagy ossza meg a hivatkozást.]]> Megerősítés esetén az üzenetküldő kiszolgálók látni fogják az IP-címét és a szolgáltatóját – azt, hogy mely kiszolgálókhoz csatlakozik. - A kép akkor érkezik meg, amikor ismerőse befejezte annak feltöltését. + A kép akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését. QR kód beolvasásával]]> Kapott SimpleX Chat meghívó hivatkozását megnyithatja böngészőjében: Ha az alkalmazás megnyitásakor az önmegsemmisítő jelkódot megadásra kerül: @@ -689,7 +689,7 @@ Különböző nevek, avatarok és átviteli izoláció. Elutasítás esetén a feladó NEM kap értesítést. Szerepkör kiválasztásának bővítése - A kép akkor érkezik meg, amikor ismerős elérhető lesz, várjon vagy ellenőrizze később! + A kép akkor érkezik meg, amikor az ismerőse elérhető lesz, várjon, vagy ellenőrizze később! meghívott Érvénytelen kapcsolati hivatkozás Némítás @@ -706,7 +706,7 @@ A tag eltávolítása a csoportból - ez nem vonható vissza! Győződjön meg róla, hogy az XFTP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. Nem kerültek ismerősök kiválasztásra - Nincsenek fogadott vagy küldött fájlok + Nincsenek fogadott, vagy küldött fájlok Megnyitás mobil alkalmazásban, majd koppintson a Csatlakozás gombra az alkalmazásban.]]> Markdown az üzenetekben meghívás a %1$s csoportba @@ -726,7 +726,7 @@ Hamarosan további fejlesztések érkeznek! Az üzenetreakciók ebben a csevegésben le vannak tiltva. Helytelen biztonsági kód! - Ez akkor fordulhat elő, ha ön vagy a kapcsolata régi adatbázis biztonsági mentést használt. + Ez akkor fordulhat elő, ha ön, vagy az ismerőse régi adatbázis biztonsági mentést használt. Új asztali alkalmazás! Most már az adminok is: \n- törölhetik a tagok üzeneteit. @@ -740,15 +740,15 @@ Új tag szerepköre Ki Érvénytelen hivatkozás! - A csatlakozáshoz Onion host-okra lesz szükség. + A csatlakozáshoz Onion kiszolgálókra lesz szükség. Változások a %s verzióban - Onion host-ok használata, ha azok rendelkezésre állnak. + Onion kiszolgálók használata, ha azok rendelkezésre állnak. Érvénytelen kiszolgálócím! k soha (új)]]> Győződjön meg arról, hogy az SMP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. - Onion host-ok nem lesznek használva. + Onion kiszolgálók nem lesznek használva. perc Tudjon meg többet Új kapcsolattartási kérelem @@ -765,7 +765,7 @@ be Japán és Portugál kezelőfelület Ebben a csoportban az üzenetek visszafordíthatatlan törlése le van tiltva. - Onion host-ok nem lesznek használva. + Onion kiszolgálók nem lesznek használva. %s eszközzel megszakadt a kapcsolat]]> hónap Üzenetvázlat @@ -777,7 +777,7 @@ Ebben a csevegésben az üzenetek visszafordíthatatlan törlése le van tiltva. Max 40 másodperc, azonnal fogadható. inkognitó a ismerős azonosító hivatkozáson keresztül - A kapcsolódáshoz Onion host-okra lesz szükség. + A kapcsolódáshoz Onion kiszolgálókra lesz szükség. \nFigyelem: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. Olasz kezelőfelület Nincsenek háttérhívások @@ -809,7 +809,7 @@ Értesítési szolgáltatás Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. 2 rétegű végponttól-végpontig titkosítással küldött üzeneteket.]]> - Érvénytelen migrációs visszaigazolás + Érvénytelen átköltöztetési visszaigazolás Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények Érvénytelen QR-kód @@ -829,12 +829,12 @@ Ha a SimpleX Chat-nek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha: \n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. -\n2. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse régebbi adatbázis biztonsági mentést használt. +\n2. Az üzenet visszafejtése sikertelen volt, mert ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt. \n3. A kapcsolat sérült. megfigyelő inkognitó a csoportos hivatkozáson keresztül - Onion host-ok használata, ha azok rendelkezésre állnak. - Barátok meghívása + Onion kiszolgálók használata, ha azok rendelkezésre állnak. + Ismerősök meghívása Menük és figyelmeztetések Tagok meghívása csatlakozás mint %s @@ -913,10 +913,10 @@ Önmegsemmisítési jelkód Mentés és csoport profil frissítése Adatvédelem - SimpleX azonosítója + SimpleX azonosító Jelentse a fejlesztőknek. Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak. - Eltűnő üzenetek küldésének letiltása. + Az eltűnő üzenetek küldése le van tiltva. Csak ön tud hangüzeneteket küldeni. Frissítés Videó elküldve @@ -931,7 +931,7 @@ Egyszer használatos hivatkozás megosztása Hiba az adatbázis visszaállításakor %s és %s - Engedélyezte + Engedélyezve Csökkentett akkumulátorhasználat Mentés és ismerősök értesítése Előnézet @@ -940,13 +940,13 @@ Fogadott üzenet Üdvözlő üzenet %s, %s és %d további tag csatlakozott - Csak az ismerős tud hívást indítani. + Csak az ismerőse tud hívást indítani. TÉMÁK Túl sok videó! Csevegési szolgáltatás megállítása az adatbázis műveletek elvégzéséhez. Üdvözöljük! Önmegsemmisítési jelkód - (beolvasás vagy beillesztés a vágólapról) + (beolvasás, vagy beillesztés a vágólapról) Videóra várakozás Válasz Ez az egyszer használatos hivatkozása! @@ -954,23 +954,23 @@ Új inkognító profil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. SimpleX - Hivatkozás előnézetek küldése + Hivatkozás előnézete biztonsági kód megváltozott Kizárólag ismerős megjelenítése Hangszóró bekapcsolva Importált csevegési adatbázis használatához indítsa újra az alkalmazást. jogosulatlan küldés - Csak az ismerős tud hangüzeneteket küldeni. + Csak az ismerőse tud hangüzeneteket küldeni. Beállítások - A csatlakozáshoz az ismerős beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. + A csatlakozáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. visszaigazolás fogadása… - Biztonsági kód beolvasása ismerős általi alkalmazásból. + Biztonsági kód beolvasása az ismerősének alkalmazásából. Lépjen kapcsolatba a csoport adminnal. Videó bekapcsolva Profil neve: Beillesztés Köszönjük, hogy telepítette a SimpleX Chatet! - Csillag a GitHub-on + Csillagozás a GitHub-on Eltávolítás Keresés Titkosítás újraegyeztetése? @@ -1039,7 +1039,7 @@ Elküldés Biztonsági kód Adja meg a helyes aktuális jelmondatát. - Az üzenetek véglegesen való törlése le van tiltva. + Az elküldött üzenetek visszafordíthatatlan törlése le van tiltva. Üzenetreakció tiltása. Véletlenszerű jelmondat használata egyenrangú @@ -1087,10 +1087,10 @@ Időszakosan indul Ez a SimpleX azonosítója! eltávolítva - Hivatkozás megosztása + Megosztás SimpleX csapat profilkép - Csevegési profiljai + Csevegési profilok tulajdonos Bekapcsolás %s, %s és %s csatlakozott @@ -1124,7 +1124,7 @@ A küldő törölhette a kapcsolódási kérelmet. Téves adatbázis jelmondat SMP kiszolgálók - Üzenet kézbesítési jelentés letiltva + Az üzenet kézbesítési jelentések le vannak tiltva Adatbázis mappa megnyitása egyszer használatos hivatkozáson keresztül Csoportbeállítások megadása @@ -1155,10 +1155,10 @@ megfigyelő szerep Port Jelkód beállítása - Milyen újdonságok vannak + Változáslista Csoport megnyitása Elküldve ekkor - Hangüzenetek küldésének letiltása. + Hangüzenetek küldése le van tiltva. Utolsó üzenetek megjelenítése Az előre beállított kiszolgáló címe Rendszeres értesítések letiltva! @@ -1170,20 +1170,20 @@ Szétkapcsolás Véletlenszerű profil Téves jelmondat! - Az üzenetreakciók tiltása. + Az üzenetreakciók küldése le van tiltva. Rendszer olvasatlan Függő Üdvözöljük %1$s! Jelmondat eltávolítása a Keystrore-ból? Feloldás - Eltűnő üzenetek küldésének letiltása. + Az eltűnő üzenetek küldése le van tiltva. Videó Frissítés Megnyitás Rendszeres értesítések Kihagyott üzenetek - Hangüzenetek küldésének letiltása. + A hangüzenetek küldése le van tiltva. Ismerős nevének beállítása... Csak ön tud eltűnő üzeneteket küldeni. Kép/videó megoszása… @@ -1207,7 +1207,7 @@ QR-kód megjelenítése videóhívás Nem kedvenc - Üzenet kézbesítési jelentések + Üzenet kézbesítési jelentések küldése SimpleX azonosító Koppintson a gombra Mentés és ismerős értesítése @@ -1221,7 +1221,7 @@ SimpleX zárolási mód Fájl visszavonása XFTP kiszolgálók - Fájlok- és a médiatartalom küldés letiltása. + A fájlok- és a médiatartalom küldése le van tiltva. Fájl megosztása… Mentés átjátszón keresztül @@ -1230,7 +1230,7 @@ Jelmondat mentése és csevegés megnyitása Beállítások mentése? Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. - Közvetlen üzenetek küldésének letiltása tagok részére. + A közvetlen üzenetek küldése a tagok számára le van tiltva. SOCKS proxy használata? Hangszóró kikapcsolva hét @@ -1243,7 +1243,7 @@ Rendszerhitelesítés Böngészőn keresztül Csevegési profiljok védelme jelszóval! - Csak az ismerős tud eltűnő üzeneteket küldeni. + Csak az ismerőse tud eltűnő üzeneteket küldeni. ICE kiszolgálók QR-kód beolvasása számítógépről SimpleX logó @@ -1260,7 +1260,7 @@ SimpleX zárolás Mentés és csoporttagok értesítése Alaphelyzetbe állítás - Csak az ismerős tud üzeneteakciókat adni. + Csak az ismerőse tud üzenetreakciókat küldeni. Hangüzenetek elhagyta Hangüzenet rögzítése @@ -1295,7 +1295,7 @@ Már csatlakozott: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! \nEz a művelet nem visszavonható - profilok, ismerősök, csevegési üzenetek és fájlok véglegesen elvesznek! - Ötletek és kérdések beküldése + Ötletek és javaslatok Figyelmeztetés: néhány adat elveszhet! Koppintson az új csevegés indításához Várakozás a számítógépre… @@ -1308,7 +1308,7 @@ fájlok fogadása egyelőre még nem támogatott Csoport profil mentése Alaphelyzetbe állítás - Hacsak az ismerős nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet – kérjük, jelentse. + Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet – kérjük, jelentse. \nA csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. videóhívás (nem e2e titkosított) Alkalmazás új kapcsolatokhoz @@ -1319,7 +1319,7 @@ Az ismerősnek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. \nMegszakíthatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással) A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonsági mentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. - Az ismerősök továbbra is csatlakoztatva maradnak. + Az ismerősei továbbra is csatlakoztatva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát Az adatbázis nem működik megfelelően. Koppintson további információért A fájl küldése leállt. @@ -1341,7 +1341,7 @@ Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen Az adatavédelem érdekében kapcsolja be a SimpleX zárolás funkciót. \nA funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befejezésére. - A videó akkor érkezik meg, amikor az ismerős elérhető, várjon, vagy ellenőrizze később! + A videó akkor érkezik meg, amikor az ismerőse elérhető, várjon, vagy ellenőrizze később! Hálózati kapcsolat ellenőrzése a következővel: %1$s, és próbálja újra. A SimpleX zárolás a Beállításokon keresztül kapcsolható be. Az alkalmazás összeomlott @@ -1361,20 +1361,20 @@ %1$s.]]> Profil felfedése Ez a hivatkozás nem érvényes kapcsolati hivatkozás! - A végpontok közötti titkosítás ellenőrzéséhez ismerősével hasonlítsa össze (vagy szkennelje be) az eszközén lévő kódot. - A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerőstől. + A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy szkennelje be) az ismerőse eszközén lévő kódot. + A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől. Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes Meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival. Ez a csoport már nem létezik. Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz. Meghívást kapott a csoportba Ismerőse a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött. - Az ismerősök és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX kiszolgálókon. + Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX kiszolgálókon. Üzenetek formázása a szövegbe szúrt speciális karakterekkel: Megnyitás alkalmazásban gombra.]]> - Csevegési profilja megküldésre kerül -\nismerőse számára - Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban + Csevegési profilja elküldésre kerül +\naz ismerőse számára + Egy olyan ismerősét próbálja meghívni, akivel inkognitó profilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban %1$s csoporthoz.]]> Amikor az alkalmazás fut Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében meghívók küldése tiltott @@ -1382,13 +1382,13 @@ Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva ebben a csoportban. Alkalmazás akkumulátor használata / Korlátlan módot az alkalmazás beállításaiban.]]> - Biztonságos kvantum ellenálló protokoll által. + Biztonságos kvantumrezisztens protokollon keresztül. - hangüzenetek 5 percig. \n- egyedi eltűnési időhatár \n- előzmény szerkesztése Használat számítógépről gombra a mobil appban és olvassa be a QR-kódot!]]> %s at %s - Akkor csatlakozik, amikor az ismerős eszköze online lesz, várjon, vagy ellenőrizze később! + Akkor csatlakozik, amikor az ismerősének az eszköze online lesz, várjon, vagy ellenőrizze később! Kéretlen üzenetek elrejtése. Használja az .onion hostokat NEM értékre, ha a SOCKS proxy nem támogatja őket.]]> Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki csatlakozhat önhöz. @@ -1397,7 +1397,7 @@ %s szerepkörét megváltoztatta erre: %s Csoport meghívó elutasítva Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz egy különbözőt. - (megosztás ismerőssel) + (megosztás egy ismerőssel) Csoport meghívó elküldve Kapcsolat izolációs mód frissítése? Kapcsolat izolációs mód @@ -1409,11 +1409,11 @@ Később engedélyezheti őket az alkalmazás Adatvédelem és biztonság menüpontban. Rejtett profiljának felfedéséhez írja be a teljes jelszót a Csevegési profilok oldal keresőmezőjébe. A csevegés frissítése és megnyitása - Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősök számára. - fogadja az üzeneteket, ismerősöket – A kiszolgálók, amelyeket az üzenetküldéshez használ.]]> + Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősei számára. + fogadja az üzeneteket, ismerősöket – a kiszolgálók, amelyeket az üzenetküldéshez használ.]]> %1$s csoport tagja.]]> Azonosítója megváltoztatva - Ismerősök engedélyezhetik a teljes üzenet törlést. + Ismerősei engedélyezhetik a teljes üzenet törlést. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. Ha engedélyezni szeretné, hogy egy mobilalkalmazás csatlakozzon a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt Profilja, ismerősök és az elküldött üzenetek az eszközön kerülnek tárolásra. @@ -1421,7 +1421,7 @@ Ez a karakterlánc nem egy meghívó hivatkozás! Új csevegés kezdése Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül! - Nem veszíti el ismerőseit, ha később törli az azonosítóját. + Nem veszíti el az ismerőseit, ha később törli az azonosítóját. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni veled! Saját szerepköre erre változott: %s @@ -1444,7 +1444,7 @@ Csoporttag részére a csatlakozási kérelem eküldésre kerül. Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. Már kért egy csatlakozást ezen az azonosítón keresztül! - Megoszthatja ezt a SimpleX azonosítót ismerősökkel, hogy kapcsolatba léphessenek %s-el . + Megoszthatja ezt a SimpleX azonosítót ismerőseivel, hogy kapcsolatba léphessenek ezzel: %s. Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat. Megjelenő üzenetet beállítása új tagok részére! Köszönet a felhasználóknak - hozzájárulás a Weblaten! @@ -1452,7 +1452,7 @@ Protokoll időkorlát KB-onként Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be. Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. - Profilja csak az ismerősök számára kerül megosztásra. + Profilja csak az ismerősei számára kerül megosztásra. Néhány kiszolgáló megbukott a teszten: Koppintson a csatlakozáshoz Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású fotók viszont megmaradnak. @@ -1476,8 +1476,8 @@ Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét. Rendszerhitelesítés helyetti beállítás. A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. - A csevegés leállítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés leállítása alatt nem tud üzeneteket fogadni és küldeni. - Jelmondat mentése a kulcstárolóban + A csevegés leállítása a csevegő adatbázis exportálásához, importálásához, vagy törléséhez. A csevegés leállítása alatt nem tud üzeneteket fogadni és küldeni. + Jelmondat mentése a kulcstárolóba Köszönet a felhasználóknak - hozzájárulás a Weblaten! Jelmondat mentése a beállításokban Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre. @@ -1505,7 +1505,7 @@ Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. A jelmondat a beállítások között egyszerű szövegként kerül tárolásra, miután megváltoztatta vagy újraindította az alkalmazást. A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói - Fogadás a + Fogadás ezen keresztül: Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez. A szerepkör \"%s\"-re fog változni. A tag új meghívót kap. profilkép helyőrző @@ -1516,12 +1516,12 @@ A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban Az előző üzenet hash-e más. - Ezek a beállítások a jelenlegi proiljára vonatkoznak + Ezek a beállítások a jelenlegi profiljára vonatkoznak Várjon, amíg a fájl betöltődik a csatolt mobilról GitHub tárolónkban.]]> hiba a tartalom megjelenítése közben hiba az üzenet megjelenítésekor - Láthatóvá teheti SimpleX ismerősök számára a Beállításokban. + Láthatóvá teheti SimpleX beli ismerősei számára a Beállításokban. Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagoknak. A beolvasott kód nem egy SimpleX hivatkozás QR-kód. A beillesztett szöveg nem egy SimpleX hivatkozás. @@ -1538,8 +1538,8 @@ Az előzmények nem kerülnek elküldésre új tagok részére. Újrapróbálkozás A fényképező nem elérhető - Utolsó 100 üzenet küldése új tagoknak. - Ne küldjön előzményeket új tagok részére. + Az utolsó 100 üzenet elküldése az új tagoknak. + Ne küldjön előzményeket az új tagok részére. Vagy mutassa meg ezt a kódot Kamera hozzáférés engedélyezése Fel nem használt meghívó megtartása? @@ -1634,4 +1634,81 @@ Videóhívás Hiba a böngésző megnyitása közben A hívásokhoz egy alapértelmezett webböngésző szükséges. Állítson be egy alapértelmezett webböngészőt az eszközön, és osszon meg további információkat a SimpleX Chat fejlesztőivel. + Hálózati beállítások megerősítése + Hiba a csevegési adatbázis exportálásakor + Alkalmaz + Archiválás és feltöltés + Feltöltés megerősítése + Hiba az adatbázis törlésekor + Az adminok egy tagot mindenki számára letilthatnak. + Minden ismerős, a beszélgetések és a fájlok biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP átjátszókra. + Alkalmazásadatok átköltöztetése + Adatbázis archiválása + Az átköltöztetés megszakítása + A csevegés átköltöztetve! + Ellenőrizze az internetkapcsolatot, és próbálja újra + Archív hivatkozás létrehozása + Adatbázis törlése erről az eszközről + Letöltés sikertelen + Archívum letöltése + Letöltési hivatkozás részletei + Engedélyezés a közvetlen csevegésekben (BÉTA)! + Jelmondat megadása + Hiba a beállítások mentésekor + Hiba az archívum letöltésekor + Hiba az archívum feltöltésekor + Hiba a jelmondat ellenőrzésekor: + Az exportált fájl nem létezik + A fájl törlésre került, vagy érvénytelen hivatkozás + %s letöltve + Archívum importálása + Feltöltés előkészítése + Adatbázis jelmondatának ellenőrzése + Jelmondat ellenőrzése + Jelmondat beállítása + Kép a képben hívások + Biztonságosabb csoportok + Használja az alkalmazást hívás közben. + Vagy illessze be az archívum hivatkozását + Az archívum hivatkozásának beillesztése + Letöltés ismét + Sikertelen importálás + Ellenőrizze, hogy a hálózati beállítások megfelelőek-e ehhez az eszközhöz. + A folytatáshoz a csevegést le kell állítani. + Csevegés leállítása + Vagy ossza meg biztonságosan ezt a fájl hivatkozást + Csevegés indítása + Nem szabad ugyanazt az adatbázist használni egyszerre két eszközön.]]> + Erősítse meg, hogy emlékszik az adatbázis jelmondatára az átköltöztetéshez. + Átköltöztetés egy másik eszközről opciót az új eszközön és szkennelje be a QR-kódot.]]> + Az átköltöztetés véglegesítése + Az átköltöztetés véglegesítése egy másik eszközön. + Letöltés előkészítése + Feltöltés ismét + %s feltöltve + A feltöltés sikertelen + Archívum feltöltése + Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, továbbá üzenetkézbesítési hibákat okozhat + Importálás ismét + szabványos végpontok közötti titkosítás + Átköltöztetés ide + Eszköz átköltöztetése + Átköltöztetés egy másik eszközre + Figyelem: az archívum törlésre kerül.]]> + Átköltöztetés egy másik eszközről + Kvantumrezisztens titkosítás + Megpróbálhatja még egyszer. + Átköltöztetés befejezve + Átköltöztetés egy másik eszközre QR-kód használatával. + Átköltöztetés + Megjegyzés: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a kapcsolataiból érkező üzenetek visszafejtését.]]> + Megpróbálhatja még egyszer. + Hibás hivatkozás + végpontok közötti kvantumrezisztens titkosítás + Ez a csevegés végpontok közötti titkosítással védett. + A költöztetési párbeszédablak megnyitása + Ez a csevegés végpontok közötti kvantumrezisztens tikosítással védett. + végpontok közötti titkosítással és sérülés utáni titkosságvédelemmel, visszautasítással és sérülés utáni helyreállítással védi.]]> + végpontok közötti kvantumrezisztens titkosítással és sérülés utáni titkosságvédelemmel, visszautasítással és sérülés utáni helyreállítással védi.]]> + Hiba az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index c82190936b..69710a68a2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1639,4 +1639,80 @@ Videochiamata Errore di apertura del browser Il browser predefinito è necessario per le chiamate. Configura il browser predefinito nel sistema, poi condividi più informazioni con gli sviluppatori. + Questa chat è protetta da crittografia end-to-end. + Questa chat è protetta da crittografia end-to-end resistente alla quantistica. + Avvia chat + Migra qui + Scaricamento dettagli del link + Scaricamento archivio + Errore di invio dell\'archivio + Archivia e carica + Gli amministratori possono bloccare un membro per tutti. + Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati. + Migrazione dati dell\'app + Applica + Archiviazione del database + Nota bene: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza.]]> + Annulla migrazione + Attenzione: l\'archivio verrà eliminato.]]> + Chat migrata! + Controlla la tua connessione internet e riprova + Migra da un altro dispositivo sul nuovo dispositivo e scansione il codice QR]]> + Conferma le impostazioni di rete + Conferma che ricordi la password del database da migrare. + Conferma caricamento + Creazione link dell\'archivio + Elimina il database da questo dispositivo + Scaricamento fallito + Attivala nelle chat dirette (BETA)! + Inserisci password + Errore di eliminazione del database + Errore di scaricamento dell\'archivio + Errore di esportazione del database della chat + Errore di salvataggio delle impostazioni + Errore di verifica della password: + Il file esportato non esiste + Il file è stato eliminato o il link non è valido + Importazione fallita + Finalizza la migrazione + Per continuare, la chat deve essere fermata. + Finalizza la migrazione su un altro dispositivo. + Importazione archivio + Link non valido + crittografia end-to-end con perfect forward secrecy, ripudio e recupero da intrusione.]]> + crittografia e2e resistente alla quantistica con perfect forward secrecy, ripudio e recupero da intrusione.]]> + Migra dispositivo + Migra da un altro dispositivo + Migra ad un altro dispositivo + Migra ad un altro dispositivo via codice QR. + Migrazione + Migrazione completata + Apri la schermata di migrazione + O incolla il link dell\'archivio + O condividi in modo sicuro questo link del file + Incolla link dell\'archivio + Chiamate picture-in-picture + Conferma che le impostazioni di rete sono corrette per questo dispositivo. + Preparazione del caricamento + crittografia e2e resistente alla quantistica + Ripeti importazione + Preparazione dello scaricamento + Crittografia resistente alla quantistica + Ripeti scaricamento + Ripeti caricamento + Gruppi più sicuri + %s scaricati + crittografia end-to-end standard + Imposta password + Arresto della chat + %s caricati + Invio fallito + Invio dell\'archivio + Usa l\'app mentre sei in chiamata. + Verifica password + Verifica password del database + Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi + Non devi usare lo stesso database su due dispositivi.]]> + Puoi fare un altro tentativo. + Puoi fare un altro tentativo. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 1d5a7b690d..7899c374b2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -37,7 +37,7 @@ אפשר לאנשי קשר לשלוח הודעות נעלמות. אפשר הודעות קוליות רק אם איש הקשר מאפשר אותן. אפשר לאנשי קשר להתקשר אליכם. - אפשר מחיקה בלתי הפיכה של הודעות שנשלחו. + אפשר למחוק הודעות שנשלחו באופן בלתי הפיך. (24 שעות) אפשר שליחת הודעות נעלמות. אפשר שליחת הודעות קוליות. מנהל @@ -47,7 +47,7 @@ הוספת שרתים על ידי סריקת קוד QR. הוסף למכשיר אחר אפשר שיחות רק אם איש הקשר מאפשר אותן. - אפשר לאנשי קשר מחיקת הודעות בלתי הפיכה רק אם הם מאפשרים לך לעשות זאת. + אפשר מחיקת הודעות בלתי הפיכה רק אם האיש קשר מאפשר את זה (24 שעות) אפשר שליחת הודעות ישירות לחברי הקבוצה. לאפשר הודעות קוליות\? אפשר לאנשי קשר לשלוח הודעות קוליות. @@ -95,7 +95,7 @@ אימות לא זמין הטוב ביותר לסוללה. התראות יוצגו רק כאשר האפליקציה מופעלת (ללא שירות רקע).]]> טוב לסוללה. שירות הרקע ייבדוק הודעות כל 10 דקות. שיחות או הודעות דחופות עלולות להתפספס.]]> - גם אתם וגם איש הקשר יכולים למחוק באופן בלתי הפיך הודעות שנשלחו. + גם אתה וגם איש הקשר שלך יכולים למחוק הודעות שנשלחו באופן בלתי הפיך. (24 שעות) גם אתם וגם איש הקשר יכולים לשלוח הודעות נעלמות. לא ניתן לקבל את הקובץ בטל תצוגה מקדימה של תמונות @@ -117,9 +117,9 @@ מתקשר… השיחה הסתיימה %1$s בטל תצוגה מקדימה של קבצים - להתחבר באמצעות קישור ליצירת קשר\? + להתחבר באמצעות קישור ליצירת קשר? התחבר - להתחבר באמצעות קישור קבוצה\? + להצטרף לקבוצה? מחובר מתחבר מתחבר… @@ -197,7 +197,7 @@ מחובר חיבור נוצר חיבור %1$d - להתחבר באמצעות קישור הזמנה\? + להתחבר דרך קישור חד-פעמי? איש הקשר כבר קיים איש הקשר וכל ההודעות יימחקו – לא ניתן לבטל זאת! התחברות באמצעות קישור / קוד QR @@ -333,7 +333,7 @@ הזינו קוד גישה הפעלת נעילת SimpleX ערוך - שם תצוגה: + שם פרופיל: ערוך תמונה הזינו סיסמה נכונה. העברת נתונים שונה באפליקציה/מסד נתונים: %s / %s @@ -389,7 +389,7 @@ שמות שונים, אווטארים ובידוד תעבורה. ישיר הודעות ישירות בין חברי קבוצה אסורות בקבוצה זו. - שם תצוגה + הזן את שמך: שם תצוגה אינו יכול להכיל רווחים. %d חודשים %d ח׳ @@ -436,7 +436,7 @@ קובץ: %s קבוצה לא פעילה פג תוקפה של ההזמנה לקבוצה - שם תצוגה של הקבוצה: + הזן שם של הקבוצה: שם מלא של הקבוצה: קישורי קבוצה קובץ @@ -495,7 +495,7 @@ הקבוצה תימחק עבורך – לא ניתן לבטל זאת! הסתר העדפות קבוצה - חברי הקבוצה יכולים למחוק באופן בלתי הפיך הודעות שנשלחו. + חברי קבוצה יכולים למחוק הודעות שנשלחו באופן בלתי הפיך. (24 שעות) חברי הקבוצה יכולים לשלוח הודעות נעלמות. חברי הקבוצה יכולים לשלוח הודעות ישירות. חברי הקבוצה יכולים לשלוח הודעות קוליות. @@ -627,7 +627,7 @@ פעיל לא כבוי - רק אתם יכולים למחוק הודעות באופן בלתי הפיך (איש הקשר שלכם יכול לסמן אותן למחיקה). + רק אתה יכול למחוק הודעות באופן בלתי הפיך (איש הקשר שלך יכול לסמן אותן למחיקה). (24 שעות) רק אתם יכולים לשלוח הודעות קוליות. רק איש הקשר שלכם יכול לשלוח הודעות קוליות. רק אתם יכולים לבצע שיחות. @@ -640,7 +640,7 @@ אין אנשי קשר להוסיף רק אתם יכולים לשלוח הודעות נעלמות. רק איש הקשר שלכם יכול לשלוח הודעות נעלמות. - רק איש הקשר שלכם יכול למחוק הודעות באופן בלתי הפיך (אתם יכולים לסמן אותן למחיקה). + רק איש הקשר שלך יכול למחוק הודעות באופן בלתי הפיך (אתה יכול לסמן אותן למחיקה). (24 שעות) טיוטת הודעה פרופילי צ׳אט מרובים שיפורים נוספים יגיעו בקרוב! @@ -761,7 +761,7 @@ כתובת שרת מוגדר מראש סיסמה להצגה התראות פרטיות - הדבק קישור שהתקבל + הדבק את הקישור שקיבלת עמית־לעמית שיחה ממתינה אנא דווחו על כך למפתחים. @@ -1156,7 +1156,7 @@ אישרת את החיבור סמל SimpleX שרתי XFTP - %1$d הודעות דולגו. + דילג על %1$d הודעות. סיסמת מסד נתונים שגויה הוזמנת לקבוצה הצטרפת לקבוצה זו @@ -1219,7 +1219,7 @@ שרתי ה־ICE שלך אתם תהיו מחוברים כאשר בקשת החיבור תאושר, אנא חכו או בידקו מאוחר יותר! הפרופיל שלך יישלח לאיש הקשר ממנו קיבלת קישור זה. - אתם תצטרפו לקבוצה אליה קישור זה מפנה ותתחברו לחברי הקבוצה. + תתחבר לכל חברי הקבוצה. שיתפת קישור חד־פעמי לזהות נסתרת הפעלת נעילה הפרופיל האקראי שלך @@ -1374,7 +1374,7 @@ שגיאה ביצירת איש קשר שימו לב: ממסרי הודעות וקבצים מחוברים דרך פרוקסי SOCKS. שיחות ושליחת תצוגות מקדימות של קישורים משתמשים בחיבור ישיר.]]> הצפין קבצים מקומיים - אפליקציית שולחן עבודה חדשה! + אפליקציה חדשה למחשב השולחני! 6 שפות ממשק חדשות האפליקציה מצפינה קבצים מקומיים חדשים (למעט סרטונים). ביטוי סיסמה אקראי מאוחסן בהגדרות כטקסט רגיל. @@ -1399,4 +1399,321 @@ ביטוי הסיסמה מאוחסן בהגדרות כטקסט רגיל. שלח הודעה ישירה מחובר ישירות + הצג קונסולה בחלון חדש + הערות פרטיות + עם הצפנת קבצים ומדיה. + ממשק משתמש בהונגרית ובטורקית + עם צריכת סוללה מופחתת. + החיבור עצר + שגיאה קריטית + שגיאה פנימית + אנא דווח על כך למפתחים: +\n%s +\n +\nמומלץ לאתחל מחדש את האפליקציה. + הערות פרטיות + שיחה קולית + שיחת וידאו + פתח מסך העברה + הרחב + הקוד שסרקת אינו קוד QR של קישור SimpleX. + תוכל להפוך אותו לגלוי לאנשי הקשר שלך ב-SimpleX דרך ההגדרות. + שם לא חוקי! + הגדר סיסמא + לא ידוע + משתתף לשעבר %1$s + הצפנה קצה-אל-קצה עמידה בפני מחשוב קוונטי + %s ו-%s + חסמת את %s + סטנדרט הצפנה קצה-אל-קצה + חסימה לכולם + לחסום משתתף להכל? + שגיאה בשליחת הזמנה + בטל נעילה + צפיה בהיסטוריה + קבוצות טובות יותר + צור קבוצה באמצעות פרופיל רנדומלי. + הצטרפות מהירה יותר והודעות אמינות יותר. + קבוצות חשאיות + קשר בין האפליקציות במכשיר הנייד והמחשב השולחני! 🔗 + הדבק קישור כדי להתחבר! + היסטוריה אחרונה ובוט משופר. + דרך פרוטוקול מאובטח עמיד בפני מחשוב קוונטי + מכשירי נייד מקושרים + הצ׳אט הועבר! + אתה יכול לנסות שוב. + אזהרה: הארכיון יימחק.]]> + נסה שוב + הצפנה עמידה בפני מחשוב קוונטי + העברת נתוני אפליקציה + אפשר בצ\'אטים ישירים (בטא)! + העבר למכשיר אחר באמצעות קוד QR. + שיחות תמונה-בתמונה + מנהל יכול לחסום את כל החברים בקבוצה + קשר מכשיר נייד + סרוק מהנייד + (גרסת המכשיר הזה %s)]]> + האם לבטל את הקישור למחשב השולחני? + אמת קוד עם המחשב השולחני + מחשבים שולחניים מקושרים + לא תואם! + אקראי + %s לא פעיל]]> + פתח פורט בחומת האש + המחשב השולחני עמוס + המחשב השולחני מנותק + %1$s.]]> + זה הקישור חד-פעמי שלך! + %1$s!]]> + קבוצה כבר קיימת! + התחל צ׳אט מחדש + או הדבק קישור ארכיון + הורדה נכשלה + מוריד פרטי קישור + קישור לא תקף + מתבצעת העברה + מכין את ההורדה + %s הורד + הורדה מחדש + אתה יכול לנסות שוב. + שגיאה בהורדה של ארכיון + הקובץ נמחק או שהקישור אינו תקף + העבר מכשיר + העבר אל מכשיר אחר + שגיאה במחיקת מסד נתונים + שגיאה בהעלאה לארכיון + קובץ ייצוא אינו קיים + כדי להמשיך, יש לעצור את הצ\'אט. + מכין את העלאה + עצירת צ׳אט + העבר לארכיון והעלאה + אשר העלאה + העברת בסיס נתונים לארכיון + %s העלה + העלאת ארכיון + בטל העברה + סיים את ההעברה + העלאה מחדש + או שתף באופן מאובטח את קישור הקובץ הזה + מחק את המסד נתונים מהמכשיר. + אזהרה: התחלת צ׳אט על מספר מכשירים אינה נתמכת ויכולה לגרום בבעיות בהעברת הודעות + התחל צ׳אט + העברה הושלמה + חייב לא להשתמש באותה מסד נתונים על שני מכשירים.]]> + תבדוק את החיבור לאינטרנט ונסה שוב + שגיאה באימות סיסמה: + אימות סיסמא של מסד נתונים + אימות סיסמא + אתה כבר מצטרף לקבוצה באמצעות הקישור הזה. + %s מהסיבה: %s]]> + %s חסר]]> + כבר מחובר! + חסום על ידי מנהל + שם התצוגה הזה אינו חוקי. אנא בחר שם אחר. + שגיאה במחיקת הערות פרטיות + טעינת צ׳אטים… + להתחבר עם %1$s? + צ׳אט חדש + או הצג את הקוד הזה + או סרוק קוד QR + שמור + ביטלת חסימה של %s + מחבר + הודעה נשמרה + האם לבטל חסימה של משתתף לכולם? + בטל חסימה לכולם + חסום על ידי מנהל + חסום + שגיאה בחסימת משתתף לכולם + שורת החיפוש מקבלת קישורי הזמנה. + הצטרף לשיחות קבוצתיות + שיפור במסירת הודעות + מחשב שולחני לא פעיל + להתחבר דרך קישור? + אנא דווח על כך למפתחים: +\n%s + האם להתחיל צ׳אט? + בטל קישור + סיום שיחה + לחץ לסריקה + צור קבוצה: כדי ליצור קבוצה חדשה.]]> + למחוק הערות פרטיות? + הוסף איש קשר + תוכל לראות את קישור ההזמנה שוב בפרטי החיבור. + האם לשמור הזמנה שלא נעשה בה שימוש? + הטקסט שהדבקת אינו קישור של SimpleX. + הצג קריאות API איטיות + אפשרויות למפתח + צור פרופיל + ו %d שאר האירועים + הגדר כתובת איש קשר חדש + לחץ לחיבור + דפדפן האינטרנט המוגדר כברירת מחדל נדרש לשיחות. אנא הגדר דפדפן ברירת מחדל במערכת, ושתף מידע נוסף עם המפתחים. + השיחה הזו מוגנת באמצעות הצפנה קצה-אל-קצה. + השיחה הזו מוגנת באמצעות הצפנה קצה-אל-קצה עמידה בפני מחשוב קוונטי. + זהו כתובת ה-SimpleX שלך! + %s]]> + כל ההודעות יימחקו- לא ניתן לשוב לאחור + כל ההודעות החדשות מ %s יהיו מוחבאות + החיבור לשולחן העבודה נמצא במצב לקוי + לתקן את השם ל-%s? + כל אנשי הקשר שלך, שיחות וקבצים יהיו מוצפנים באופן מאובטח ויעלו בחתיכות לריליים של XFTP שהוגדרו. + החל + קוד עבור אפליקציה + הוסף איש קשר: כדי ליצור קישור הזמנה חדש, או להתחבר דרך קישור שקיבלת.]]> + שים לב: שימוש באותו מסד נתונים על שני מכשירים ישבור את הפענוח ההצפנה של ההודעות מהחיבורים שלך, נועד כהגנה בטחונית.]]> + הצ\'אט נסגר. אם כבר השתמשת במסד נתונים זה במכשיר אחר, עליך להעבירו חזרה לפני שתתחיל בצ\'אט. + העבר ממכשיר אחר במכשיר החדש וסרוק קוד QR.]]> + אשר הגדרות רשת + אשר שאתה זוכר את סיסמת מסד הנתונים כדי להעביר אותו. + התחבר למחשב השולחני + יוצר קישור ארכיון + יוצר קישור… + מחק והודע לאיש קשר + האם למחוק %d הודעות? + למחשב השולחני יש גרסה שאינה נתמכת. אנא, ודא שאתה משתמש באותה גרסה על שני המכשירים + למחשב השולחני יש קוד הזמנה שגוי + מוריד ארכיון + אפשר גישה למצלמה + שגיאת משא ומתן מחדש של הצפנה + המשא ומתן מחדש של ההצפנה נכשל. + הזן סיסמא + שגיאה + שגיאה ביצירת הודעה + שגיאה בייצוא מסד נתוני צ\'אט + שגיאה בפתיחת דפדפן + שגיאה בשמירת אפשרויות + סיים את ההעברה על מכשיר נוסף. + קוד QR לא חוקי + ההיסטוריה לא נשלחת לחברים חדשים. + יבוא נכשל + מייבא ארכיון + האם להצטרף אל הקבוצה? + (חדש)]]> + משתתף %1$s השתנה ל-%2$s + הצפנה מקצה לקצה עם סודיות קדימה מושלמת, הכחשה ושחזור לאחר פריצה.]]> + הצפנה מקצה לקצה עמידה בפני מחשוב קוונטי עם סודיות קדימה מושלמת, הכחשה ושחזור לאחר פריצה.]]> + העבר ממכשיר אחר + העבר לכאן + %s תפוס]]> + ניתן לגילוי דרך רשת מקומית + מצלמה לא זמינה + אין מכשיר נייד מחובר + הדבק לינק ארכיון + אנא אשר שהגדרות הרשת נכונות למכשיר זה. + רענון + יבוא מחדש + קבוצות בטוחות יותר + אימות חיבור + כדי לאפשר לאפליקציה במכשיר הנייד להתחבר למחשב השולחני, פתח את הפורט הזה בחומת האש שלך, אם היא מופעלת + כבר ביקשת להתחבר באמצעות כתובת זו! + ביטלת חסימה של %s + בטל חסימה של משתתף + האם לבטל חסימה עבור משתתף? + העלאה נכשלה + השתמש באפליקציה במהלך השיחה. + הצג שגיאות פנימיות + שלח עד 100 הודעות אחרונות לחברים חדשים. + אל תשלח היסטוריה לחברים חדשים. + עד 100 ההודעות האחרונות נשלחות לחברים חדשים. + קוד סשן + %s התנתק]]> + %s נמצא במצב לקוי]]> + %s משתמש בגרסה שאינה נתמכת. אנא, ודא שאתה משתמש באותה גרסה על שני המכשירים]]> + שם תצוגה לא חוקי! + חפש או הדבק קישור של SimpleX + אמת קוד במכשיר הנייד + החיבור עצר + נתיב קובץ לא חוקי + שיתפת נתיב קובץ לא חוקי. דווח על הבעיה למפתחי האפליקציה. + %1$d הודעות שנערכו על ידי %2$s + %d הודעות סומנו כנמחקות + האם לחזור על בקשת החיבור? + חסום + %d הודעות נחסמו + אנא המתן בזמן שהקובץ נטען מהמכשיר הנייד המקושר + האם להסיר משתתף? + מנותק + האם לנתק את המחשב השולחני? + נתק מכשירי נייד + רק מכשיר אחד יכול לעבוד בו זמנית. + ממתין למחשב השולחני… + שימוש ממחשב שולחני באפליקציה בנייד וסרוק קוד QR.]]> + העברת מסד הנתונים בתהליך. +\nזה עשוי לקחת כמה דקות. + %d הודעות נחסמו על ידי מנהל + מסך קרס + לא ניתן לפענח את הווידאו. אנא נסה וידאו אחר או צור קשר עם המפתחים. + שתף את הקישור הזמנה החד-פעמי הזה + לחץ להדבקת קישור + טעינה של הקובץ + שימוש ממחשב שולחני + חסומים %s + מחק איש קשר + %d אירועי קבוצה + %s, %s ו-%d חברים + איש הקשר %1$s השתנה ל-%2$s + כתובת איש קשר הוסרה + תמונת פרופיל הוסרה + הגדר תמונת פרופיל חדשה + עדכן פרופיל + מצב לא ידוע + נוצר ב + נוצר בתאריך: %s + הודעה ארוכה מדי + הודעת ברוכים הבאים ארוכה מדי + התחבר לנייד + התחבר למחשב השולחני + התחבר לנייד + התחבר למחשב השולחני + חיבור למחשב השולחני + גרסת אפליקציית שולחן העבודה %s אינה תואמת לאפליקציה זו. + מכשירים + שגיאה + גירסא לא מתאימה + מצא מחשב שולחני + מכשיר נייד חדש + השם של המכשיר הזה + התחבר אוטומטית + שגיאה בהצגת הודעה + שגיאה בהצגת תוכן + %s מחובר + חסום + חסום משתתף + לחסום משתתף? + הודעות מ-%s יוצגו! + צור קבוצה + המכשיר הזה + - אפשרות להודיע לאנשי קשר שנמחקו. +\n- שמות פרופיל עם רווחים. +\n- ועוד! + כדי להסתיר הודעות לא רצויות. + תחסום חברים בקבוצה + מחשב שולחני + כתובת של המחשב השולחני + הדבק כתובת של המחשב שולחני + סרוק קוד QR מהמחשב השולחני + להתחבר אל עצמך? + אימות חיבורים + גלה דרך רשת מקומית + האפשרויות של המחשב השולחני מקושרות + מחשבים שולחניים + אתה כבר מתחבר באמצעות קישור חד-פעמי זה! + פתח קבוצה + %1$s.]]> + האם לחזור על בקשת הצטרפות? + הכתובת של המחשב השולחני שגויה + %s התנתק]]> + שם המכשיר ישותף עם מכשיר הנייד המחובר. + הזן את שם המכשיר הזה… + ביצוע הפונקציה לוקח זמן רב מדי: %1$d שניות: %2$s + פונקציה איטית + החיבור הופסק + נותק מהסיבה: %s + ממתין לחיבור עם מכשיר נייד: + צור פרופיל צאט + כבר הצטרפת לקבוצה! + %1$s.]]> + הסר משתתף + הסתיים פסק הזמן הקצוב להתחברות למחשב השולחני \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 7ab087612b..7bc282de3a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -191,7 +191,7 @@ サーバ設定の向上 プライバシーとセキュリティ強化 シークレットモード - %s バージョンアップで新しい + %s の新機能 新しい暗証フレーズ 一度も 新しいメッセージ @@ -838,7 +838,7 @@ グループ招待が届きました TCP接続タイムアウト - 保存グループのプロフィール + グループプロフィールの保存 連絡先がメッセージの完全削除を許可できます。 同じ表示名前のチャットプロフィールが既にあります。別のを選んでください。 セキュアな未送信メッセージ @@ -1593,4 +1593,127 @@ 遅延が発生した機能 遅いAPIコールを表示 ステータス不明 + プライベートノート + メッセージ配信の改善 + エンドツーエンドの暗号化によって保護されます。]]> + 耐量子E2E暗号化によって保護されます。]]> + このチャットはエンドツーエンド暗号化により保護されています。 + このチャットは耐量子エンドツーエンド暗号化により保護されています。 + プライベートノート + 通話終了 + ブラウザ起動エラー + 別の端末から移行 + 暗証フレーズを設定 + %s をブロック済 + 管理者によりブロック済 + 暗号化されたファイルとメディア + リンクの貼り付けで接続する! + 検索バーに招待リンクを貼り付けて接続。 + 耐量子暗号化チャット + ピクチャー イン ピクチャー 通話 + QRコードを利用して別のデバイスに移行できます。 + より安全なグループ + ここから移行 + 移行中 + ダウンロードの準備中 + 無効なリンク + ダウンロード失敗 + 再ダウンロード + もう一度お試し下さい。 + %s ダウンロード済 + アーカイブをインポート中 + インポート失敗 + チャット移行済み! + 別のデバイスで移行を確定してください。 + ファイルが削除されているか、リンクが無効です + アーカイブダウンロードエラー + 適用 + 端末の移行 + 別の端末に移行 + 設定保存エラー + チャットデータベースエクスポートエラー + アップロード準備中 + データベース削除エラー + アーカイブアップロードエラー + 続けるには、チャットを停止する必要があります。 + アーカイブとアップロード + アップロードの確認 + %s アップロード中 + アップロード失敗 + データベースをアーカイブ中 + アーカイブのアップロード中 + 移行の中止 + 移行の確定 + アーカイブリンクの作成中 + この端末からデータベースを削除 + ファイルリンクで安全に共有する + 移行完了 + チャット開始 + できません。]]> + パスフレーズの検証 + データベースパスフレーズの検証 + インターネット接続を確認して再試行してください + 移行するデータベースのパスフレーズを覚えていることを確認して下さい。 + 警告: アーカイブデータは削除されます。]]> + パスフレーズ検証エラー: + プライベートノート削除エラー + 送信日: %s + 全てをブロック + このメンバーをブロックしますか? + ブロック中 + グループ会話への参加 + 音声通話 + ビデオ通話 + 管理者によりブロック済 + アプリデータの移行 + グループ管理者は、他のすべてのメンバーに対してメンバーをブロックできます。 + 通話中にアプリが利用できます。 + ダイレクトチャットで有効にする(ベータ版)! + ウェルカムメッセージが長すぎます + 全てのメッセージが削除されます - これは元に戻せません! + 別の端末に移行を選択し、QRコードをスキャンします。]]> + すべての連絡先、会話、ファイルは安全に暗号化され、設定されたXFTPリレーに分割でアップロードされます。 + ブロックの解除 + ブロックを解除しますか? + %d のメッセージが管理者によりブロック済 + %s のブロックを解除 + パスフレーズの入力 + エクスポートされたファイルが存在しません + 送信日 + メッセージが長すぎます + データベース移行の進行中。 +\nこれには数分掛かる場合があります。 + プライベートノートを消しますか? + ハンガリー語、トルコ語 + バッテリー使用量低減 + 最近のメッセージ履歴送信機能追加とディレクトリボットの改善が行われました。 + リンクの詳細をダウンロード中 + アーカイブをダウンロード中 + メッセージ作成エラー + %s のブロックを解除 + アドレスが削除されました + アーカイブリンクを貼り付け + アーカイブリンクの貼り付け + 再インポート + もう一度お試し下さい。 + 警告:複数端末でのチャット利用はサポートされていません。 + 新しいアドレスが設定されました + メンバーブロックエラー + %s をブロック済 + 標準的のエンドツーエンド暗号化 + 耐量子エンドツーエンド暗号化 + 再アップロード + 連絡先 %1$s が %2$s に変更されました + 新しいプロフィール画像が設定されました + プロフィール画像が削除されました + 移行画面を開く + 通話には既定のウェブブラウザが必要です。既定のブラウザを設定し、開発者へ情報を共有してください。 + プロフィール更新済 + メンバーの %1$sが %2$sに名前変更済 + ネットワーク設定を確認してください + この端末のネットワーク設定が正しいことを確認してください。 + 注意: 2つの端末で同じデータベースを使用すると、セキュリティ保護として、あなたが接続しているメッセージの復号化が解除されます。]]> + メッセージ保存済 + チャット停止中 + エラーが表示された場合は、開発者に連絡してください。 \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 0de26f0d68..ecfaf72a18 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -595,7 +595,7 @@ voorgesteld %s Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u deze kwijtraakt. - Gesprekken openen + Chat openen Voer het vorige wachtwoord in na het herstellen van de database back-up. Deze actie kan niet ongedaan gemaakt worden. Oproep in behandeling Het openen van de link in de browser kan de privacy en beveiliging van de verbinding verminderen. Niet vertrouwde SimpleX links worden rood weergegeven. @@ -1498,7 +1498,7 @@ Groepsleden blokkeren Maak een groep met een willekeurig profiel. Koppel mobiele en desktop-apps! 🔗 - Via een beveiligd kwantumbestendig protocol. + Via een beveiligd quantum bestendig protocol. Om ongewenste berichten te verbergen. Betere groepen Incognitogroepen @@ -1529,16 +1529,16 @@ Opnieuw proberen Camera niet beschikbaar Stuur tot 100 laatste berichten naar nieuwe leden. - Contact toevoegen: om een nieuwe uitnodigingslink te maken, of om verbinding te maken via een link die u heeft ontvangen.]]> + Contact toevoegen: om een nieuwe uitnodigings link te maken, of om verbinding te maken via een link die u heeft ontvangen.]]> Stuur geen geschiedenis naar nieuwe leden. Of laat deze code zien Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden. De code die u heeft gescand is geen SimpleX link QR-code. De tekst die u hebt geplakt is geen SimpleX link. Schakel cameratoegang in - U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails. + U kunt de uitnodigings link opnieuw bekijken in de verbindings details. Ongebruikte uitnodiging bewaren? - Deel deze eenmalige uitnodigingslink + Deel deze eenmalige uitnodigings link Groep aanmaken: om een nieuwe groep aan te maken.]]> Zichtbare geschiedenis App toegangscode @@ -1606,7 +1606,7 @@ contactpersoon %1$s gewijzigd in %2$s Fout bij verwijderen van privénotities Privénotities verwijderen? - Zoekbalk accepteert uitnodigingslinks. + Zoekbalk accepteert uitnodigings links. ‐Met versleutelde bestanden en media. Met verminderd batterijgebruik. Opgeslagen bericht @@ -1637,4 +1637,81 @@ Video oproep Fout bij het openen van de browser Voor oproepen is de standaard webbrowser vereist. Configureer de standaard browser in het systeem en deel meer informatie met de ontwikkelaars. + Bevestig netwerk instellingen + Archiveren en uploaden + Database archiveren + Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays. + Toepassen + Let op: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel.]]> + Waarschuwing: het archief wordt verwijderd.]]> + Migratie annuleren + Chat gemigreerd! + Controleer uw internetverbinding en probeer het opnieuw + Migreren vanaf een ander apparaat op het nieuwe apparaat en scan de QR-code.]]> + Beheerders kunnen een lid voor iedereen blokkeren. + Migratie van app-gegevens + Archief link maken + Verwijder de database van dit apparaat + Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren. + Activeer in directe chats (BETA)! + Link gegevens downloaden + Fout bij opslaan van instellingen + Fout bij verwijderen database + Bevestig het uploaden + Download mislukt + Archief downloaden + Voer het wachtwoord in + Fout bij het downloaden van het archief + Fout bij het exporteren van de chat database + Fout bij het uploaden van het archief + Deze chat is beveiligd met end-to-end codering. + standaard end-to-end encryptie + Voltooi de migratie op een ander apparaat. + Om verder te kunnen gaan, moet de chat worden gestopt. + Herhaal het uploaden + Upload mislukt + Je kunt het nog een keer proberen. + Voltooi de migratie + Chat starten + Fout bij het verifiëren van het wachtwoord: + quantum bestendige encryptie + Beeld-in-beeld oproepen + Veiligere groepen + Gebruik de app tijdens het gesprek. + Migreren + Of plak de archief link + Downloaden voorbereiden + Herhaal het downloaden + Je kunt het nog een keer proberen. + Archief importeren + Herhaal import + Bestand is verwijderd of de link is ongeldig + Geëxporteerd bestand bestaat niet + Apparaat migreren + Migreer naar een ander apparaat + Chat stoppen + %s geüpload + Archief uploaden + mag niet dezelfde database op twee apparaten gebruiken.]]> + Controleer het wachtwoord + Controleer het wachtwoord van de database + Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten + Importeren is mislukt + Ongeldige link + end-to-end-codering met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel.]]> + quantum bestendige e2e-encryptie met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel.]]> + Migreer vanaf een ander apparaat + Migreer hierheen + Migreer naar een ander apparaat via QR-code. + Migratie voltooid + Open het migratiescherm + Of deel deze bestands link veilig + Archief link plakken + Controleer of de netwerk instellingen correct zijn voor dit apparaat. + Uploaden voorbereiden + quantum bestendige e2e-codering + %s gedownload + Wachtwoord instellen + Deze chat wordt beschermd door quantum bestendige end-to-end codering. + Fout bij weergeven van melding. Neem contact op met ontwikkelaars. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 00089a53a3..04e40e73cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -1639,4 +1639,80 @@ Połączenie wideo Błąd podczas otwierania przeglądarki Do połączeń wymagana jest domyślna przeglądarka. Proszę skonfigurować domyślną przeglądarkę systemową, i podzielić się informacją z twórcami. + Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end. + szyfrowanie end-to-end z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu.]]> + Otwórz ekran migrowania + Zmigruj z innego urządzenia + Ustaw hasło + standardowe szyfrowanie end-to-end + Czat zmigrowany! + Migracja danych aplikacji + Bezpieczniejsze grupy + Używaj aplikacji podczas połączenia. + Połączenia obraz-w-obrazie + Administratorzy mogą blokować członka dla wszystkich. + Nieprawidłowy link + Zmigruj tutaj + Migrowanie + Lub wklej link archiwum + Wklej link archiwum + Przygotowywanie pobrania + %s pobrane + Możesz spróbować ponownie. + Importowanie archiwum + Powtórz pobieranie + Import nie udał się + Powtórz importowanie + Błąd pobierania archiwum + Dokończ migrację na innym urządzeniu. + Zastosuj + Zmigruj urządzenie + Zmigruj do innego urządzenia + Przygotowywanie wgrania + Błąd usuwania bazy danych + Zatrzymywanie czatu + Aby konturować, czat musi zostać zatrzymany. + Archiwizowanie bazy danych + Potwierdź wgranie + %s wgrane + Wgrywanie nie udane + Wgrywanie archiwum + Powtórz wgrywanie + Tworzenie linku archiwum + Możesz spróbować ponownie. + Lub bezpiecznie udostępnij ten link pliku + Rozpocznij czat + Migracja zakończona + Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować. + Zweryfikuj hasło bazy danych + Sprawdź swoje połączenie z internetem i spróbuj ponownie + Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP. + Archiwizuj i prześlij + Uwaga: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie.]]> + Ostrzeżenie: archiwum zostanie usunięte.]]> + Anuluj migrację + Zmigruj z innego urządzenia na nowym urządzeniu i zeskanuj kod QR.]]> + Potwierdź ustawienia sieciowe + Usuń bazę danych z tego urządzenia + Pobieranie nie udane + Pobieranie archiwum + Pobieranie szczegółów linku + Włącz w czatach bezpośrednich (BETA)! + Wprowadź hasło + Błąd eksportu bazy danych czatu + Błąd zapisywania ustawień + Błąd wgrywania archiwum + Błąd weryfikowania hasła: + Wyeksportowany plik nie istnieje + Ten czat jest chroniony przez szyfrowanie end-to-end. + Zweryfikuj hasło + Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości + Nie możesz używać tej samej bazy na dwóch urządzeniach.]]> + Plik został usunięty lub łącze jest nieprawidłowe + Dokończ migrację + kwantowo odporne szyfrowanie end-to-end z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu.]]> + Zmigruj do innego urządzenia przez kod QR. + Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia. + kwantowo odporne szyfrowanie e2e + Kwantowo odporne szyfrowanie \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 438d6832c9..7889ef396e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -376,7 +376,7 @@ Ligação de conexão inválida Salvar Se você recebeu convite de ligação do SimpleX Chat, você pode abri-lo no seu navegador: - Toque para entrar em modo anónimo + Toque para entrar no modo anônimo Salvar senha na Keystore mostre o código QR na chamada de vídeo ou partilhe a ligação.]]> Salvar definições de aceitação automática @@ -733,4 +733,51 @@ activado para si Pesquisar Desativado + O teste falhou na etapa %s. + Servidor SMP + Servidor de teste + %s segundo(s) + Parar conversa? + Sistema + Suporte a bluetooth e outras melhorias. + Parando a conversa + %s enviado + Iniciar conversa + Toque para colar o link + Parar de compartilhar o endereço? + iniciando… + Pare a conversa para exportar, importar ou excluir o banco de dados de conversa. Você não poderá receber e enviar mensagens enquanto a conversa for interrompida. + %s, %s e %d outros membros conectados + Iniciar conversa? + Começa periodicamente + Toque para iniciar uma nova conversa + Sistema de autenticação + Parar de receber o arquivo? + Servidores de teste + Alguns servidores falharam no teste: + Parar compartilhamento + Mensagens ignoradas + Grupos pequeno (máx. 20) + Alguns erros não fatais ocorreram durante a importação - você pode ver o console de conversa para obter mais detalhes. + encriptação end-to-end padrão + Toque no botão + Toque para ativar o perfil. + Parar de enviar o arquivo? + Obrigado aos usuários – contribuam via Weblate! + Pare + Pare + Chamada rejeitada + Parar arquivo + Toque para Conectar + %s, %s e %d membros + Iniciar nova conversa + Sistema + Parar conversa para habilitar ações do banco de dados + Toque para participar + %s, %s e %s conectado + Tempo esgotado da conexão TCP + Sistema + Parar conversa + Obrigado aos usuários – contribuam via Weblate! + Função lenta \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 98e6223d87..f855bb040b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -343,7 +343,7 @@ Настройки Ваш SimpleX адрес База данных - Информация о SimpleX Chat + Подробнее о SimpleX Chat Как использовать Форматирование сообщений Форматирование сообщений @@ -768,7 +768,7 @@ Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль Пригласить членов группы - ЧЛЕНОВ ГРУППЫ: %1$s + УЧАСТНИКОВ ГРУППЫ: %1$s Вы: %1$s Удалить группу Удалить группу? @@ -1337,7 +1337,7 @@ шифрование работает для %s требуется новое соглашение о шифровании для %s Изменение адреса будет прекращено. Будет использоваться старый адрес. - Прекратить изменение адреса + Остановить изменение адреса Контакты Выключить (кроме исключений) шифрование согласовывается… @@ -1359,7 +1359,7 @@ Вы можете включить их позже в настройках Конфиденциальности. Ошибка при прекращении изменения адреса Прекратить - Прекратить изменение адреса\? + Остановить изменение адреса? Не избранный Нотификации перестанут работать, пока вы не перезапустите приложение Таймаут протокола на KB @@ -1713,4 +1713,89 @@ Ошибка при блокировании члена для всех Разблокировать члена для всех? Вы заблокировали %s + end-to-end шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + Чат защищен end-to-end шифрованием. + Чат защищен квантово-устойчивым end-to-end шифрованием. + Открыть экран миграции + Миграция с другого устройства + Установить пароль + стандартное end-to-end шифрование + Приветственное сообщение слишком длинное + Сообщение слишком большое + Повторить загрузку + Вы можете попробовать еще раз. + Загрузка архива + Подготовка архива + %s загружено + Запустить чат + Миграция данных + Включите для контактов (BETA)! + Более безопасные группы + Админы могут заблокировать члена группы. + Вставьте ссылку архива + Ошибка ссылки + Миграция + Загрузка архива + Загрузка ссылки архива + Подготовка загрузки + Ошибка загрузки + %s загружено + Импорт архива + Ошибка импорта + Чат мигрирован! + Введите пароль + Ошибка загрузки архива + Повторить импорт + Пожалуйста, подтвердите, что настройки сети верны для этого устройства. + Применить + Мигрировать устройство + Ошибка сохранения настроек + Ошибка при экспорте архива чата + Подготовка загрузки + Ошибка загрузки архива + Ошибка при удалении данных чата + Остановка чата + Архивировать и загрузить + Подтвердить загрузку + Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы. + Ошибка загрузки + Повторить загрузку + Вы можете попробовать еще раз. + Отменить миграцию + Мигрировать с другого устройства на новом устройстве и сосканируйте QR код.]]> + Создание ссылки на архив + Удалить базу данных с этого устройства + Завершить миграцию + Или передайте эту ссылку + Миграция завершена + Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений. + не должны использовать одну и ту же базу данных на двух устройствах.]]> + Проверьте подключение к Интернету и повторите попытку + Подтвердите, что Вы помните пароль базы данных для ее миграции. + Проверка пароля базы данных + Проверить пароль + Ошибка подтверждения пароля: + Аудиозвонок + Завершить звонок + Видеозвонок + Чтобы продолжить, чат должен быть остановлен. + Обратите внимание: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений.]]> + Внимание: архив будет удален.]]> + Подтвердите настройки сети + квантово-устойчивым end-to-end шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + Мигрировать сюда + Мигрировать на другое устройство + Мигрируйте на другое устройство через QR код. + Или вставьте ссылку архива + Звонки с картинкой-в-картинке + квантово-устойчивое e2e шифрование + Квантово-устойчивое шифрование + Используйте приложение во время звонка. + Экспортированный файл не существует + Файл удален или ошибка ссылки + Завершите миграцию на другом устройстве. + Выполняется миграция базы данных. +\nЭто может занять несколько минут. + Ошибка открытия браузера + Для звонков требуется веб-браузер по умолчанию. Пожалуйста, настройте браузер по умолчанию в системе и поделитесь дополнительной информацией с разработчиками. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 3c8b8b31cf..4464e323f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -1289,7 +1289,7 @@ Dosya gönderimi durdurulacaktır. yönetildi Üye bağlantısı oluşturulurken hata - Çoklu gönderim bütün kişiler için etkinleştirilecektir. + Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek XFTP sunucu adreslerinin doğru formatta olduğundan, satırın ayrılmış ve kopyalanmamış olduğundan emin olun. Yenile Lütfen unutmayın: mesaj ve dosya yönlendiricileri SOCKS vekili tarafından bağlandı. Aramalar ve bağlantı ön gösterimleri doğrudan bağlantı kullanıyor.]]> @@ -1640,4 +1640,80 @@ Görüntülü arama Aramayı bitir Tarayıcıyı açarken hata + uçtan uca şifreleme ile korunmaktadır.]]> + Bu sohbet uçtan uca şifrelemeyle korunmaktadır. + Taşıma ekranını aç + Başka bir cihazdan taşı + Parolayı ayarla + Veya arşiv bağlantısını yapıştırın + Sohbet taşındı! + Uygula + Taşıma işlemini sonlandır + Doğrudan sohbetlerde etkinleştir (BETA)! + Kuantuma dayanıklı şifreleme + Yöneticiler bir üyeyi tamamen engelleyebilirler. + Uygulama veri göçü. + QR kodu aracılığıyla başka bir cihaza geçiş yapın. + Resim içinde resim çağrıları + Daha güvenli gruplar + Arama sırasında uygulamayı kullanın. + Buraya taşı + Arşiv bağlantısını yapıştır + İndirmeye hazırlanıyor + Yükleme başarısız + Arşiv indiriliyor + Bağlantı detayları indiriliyor + İndirmeyi tekrarla + %s indirildi + İçe aktarma başarısız oldu + Arşiv içe aktarılıyor + İçe aktarmayı tekrarla + Bir kez daha deneyebilirsiniz. + Arşiv indirilirken hata oluştu + Taşıma işlemini başka bir cihazda sonlandırın. + Dışa aktarılan dosya mevcut değil + Cihazı taşı + Başka bir cihaza geçiş + Veritabanı silinirken hata oluştu + Sohbet veritabanı dışa aktarılırken hata oluştu + Arşiv yüklenirken hata oluştu + Yükleme hazırlanıyor + Yüklemeyi onayla + Devam etmek için sohbetin durdurulması gerekiyor. + Veritabanını arşivleme + Yüklemeyi tekrarla + %s yüklendi + Yükleme başarısız + Arşiv yükleniyor + Lütfen dikkat: aynı veritabanını iki cihazda kullanmak, bir güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir.]]> + Taşımayı iptal et + Arşiv bağlantısı oluşturuluyor + Taşıma işlemi tamamlandı + Veya bu dosya bağlantısını güvenli bir şekilde paylaşın + Sohbeti başlat + Uyarı: Birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj iletimi başarısızlıklara neden olabilir. + Veritabanı parolasını doğrulayın + Parolayı doğrulayın + Tüm kişileriniz, konuşmalarınız ve dosyalarınız güvenli bir şekilde şifrelenir ve yapılandırılmış XFTP rölelerine parçalar halinde yüklenir. + Arşivle ve yükle + Uyarı: arşiv silinecektir.]]> + Taşımak için veritabanı parolasını hatırladığınızı doğrulayın. + İnternet bağlantınızı kontrol edip tekrar deneyin + Ağ ayarlarını onaylayın + Veritabanını bu cihazdan sil + Parolayı girin + Ayarlar kaydedilirken hata oluştu + Parola doğrulanırken hata oluştu: + Dosya silindi veya bağlantı geçersiz + Geçersiz link + kuantum dirençli e2e şifreleme ile korunmaktadır.]]> + Taşınıyor + Lütfen bu cihaz için ağ ayarlarının doğru olduğunu onaylayın. + kuantum dirençli e2e şifreleme + standart uçtan uca şifreleme + Sohbet durduruluyor + Bu sohbet kuantum dirençli uçtan uca şifrelemeyle korunmaktadır. + kullanmamalısınız.]]> + Bir kez daha deneyebilirsiniz. + Başka bir cihazdan taşı\'yı seçin ve QR kodunu tarayın.]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 8aebe017ec..948f91b89a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -595,7 +595,7 @@ Відео Ваш контакт відправив файл, розмір якого більший, ніж поточно підтримуваний максимальний розмір (%1$s). Поточно максимально підтримуваний розмір файлу - %1$s. - Адреса для отримання буде змінена на інший сервер. Зміна адреси завершиться після включення відправника. + Адреса отримувача буде змінена на інший сервер. Зміна адреси завершиться після того, як відправник з\'явиться в мережі. Перевірити код безпеки Надіслати повідомлення Записати голосове повідомлення @@ -1637,4 +1637,80 @@ Аудіодзвінок Помилка відкриття браузера Для використання дзвінків потрібен браузер за замовчуванням. Будь ласка, налаштуйте браузер за замовчуванням в системі та надайте більше інформації розробникам. + Перехід з іншого пристрою + квантово-стійке шифрування e2e + стандартне наскрізне шифрування + Цей чат захищений наскрізним шифруванням. + Цей чат захищений квантово-стійким наскрізним шифруванням. + Адміністратори можуть заблокувати користувача для всіх. + Міграція даних додатків + Перейдіть на інший пристрій за допомогою QR-коду. + Дзвінки картинка в картинці + Квантово-стійке шифрування + Безпечніші групи + Невірне посилання + Мігруйте сюди + Міграція + Або вставте посилання на архів + Вставити посилання на архів + Не вдалося завантажити + Деталі посилання для завантаження + Підготовка до завантаження + Повторити завантаження + %s завантажено + Введіть парольну фразу + Файл було видалено або посилання недійсне + Не вдалося імпортувати + Імпорт архіву + Повторний імпорт + Завершіть міграцію на іншому пристрої. + Подати заявку + Перенести пристрій + Перехід на інший пристрій + Помилка експорту бази даних чату + Налаштування збереження помилок + Помилка завантаження архіву + Експортований файл не існує + Архівування та завантаження + Архівування бази даних + Підтвердити завантаження + Помилка видалення бази даних + Для того, щоб продовжити, чат слід зупинити. + Зупинка чату + %s завантажено + Завантаження архіву + Створення архівного посилання + Видалити базу даних з цього пристрою + Завершити міграцію + Або безпечно поділіться цим посиланням на файл + Повторити завантаження + Ви можете спробувати ще раз. + Почати чат + Попередження: запуск чату на декількох пристроях не підтримується і може призвести до збоїв у доставці повідомлень + Міграція завершена + Перевірте пароль до бази даних + не повинні використовувати одну і ту ж базу даних на двох пристроях.]]> + Warning: архів буде видалено.]]> + Перевірте підключення до Інтернету та спробуйте ще раз + Переконайтеся, що ви пам\'ятаєте пароль до бази даних для її перенесення. + Помилка при перевірці парольної фрази: + Всі ваші контакти, розмови та файли будуть надійно зашифровані та завантажені частинами на налаштовані XFTP-реле. + Please note: використання однієї і тієї ж бази даних на двох пристроях порушить розшифровку повідомлень з ваших з\'єднань, як захист безпеки.]]> + Скасувати міграцію + Чат перемістився! + Migrate from another device на новому пристрої та відскануйте QR-код.]]> + Підтвердьте налаштування мережі + Завантажити архів + Увімкнути в прямих чатах (BETA)! + Помилка завантаження архіву + наскрізним шифруванням з ідеальною секретністю переадресації, відмовою та відновленням після злому.]]> + квантово-стійким шифруванням e2e з ідеальною прямою секретністю, відмовою та відновленням після злому.]]> + Відкрийте екран міграції + Переконайтеся, що налаштування мережі для цього пристрою є правильними. + Підготовка до завантаження + Встановити парольну фразу + Не вдалося завантажити + Використовуйте додаток під час розмови. + Підтвердіть парольну фразу + Ви можете спробувати ще раз. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 693a261be1..d502c35e9e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -85,4 +85,15 @@ và %d sự kiện khác Android Keystore được sử dụng để lưu trữ passphrase - nó cho phép dịch vụ thông báo hoạt động. Android Keystore sẽ được sử dụng để lưu trữ passphrase một cách an toàn sau khi bạn khởi động lại ứng dụng hoặc thay đổi passphrase - nó cho phép tiếp nhận thông báo. + Tất cả các liên hệ, cuộc hội thoại và tệp của bạn sẽ được mã hóa an toàn và tải lên từng phần tới các XFTP relay được chỉ định. + Quản trị viên có thể chặn một thành viên khỏi tất cả. + Một hồ sơ trống với tên chỉ định đã được tạo, và ứng dụng sẽ mở ra như bình thường. + Trả lời cuộc gọi + Một hồ sơ ngẫu nhiên mới sẽ được chia sẻ. + Ứng dụng chỉ có thể nhận thông báo khi nó đang chạy, không có dịch vụ nền nào được khởi động + Bản dựng ứng dụng: %s + Giao diện + ỨNG DỤNG + Di chuyển dữ liệu ứng dụng + Sao lưu dữ liệu ứng dụng \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 3873aac128..3298da5097 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1639,4 +1639,80 @@ 结束通话 打开浏览器出错 没有默认网络浏览器无法使用通话功能。请在系统中配置默认浏览器并和开发者分享更多信息。 + 此聊天受抗量子的端到端加密保护。 + 此聊天受端到端加密保护。 + 打开迁移屏幕 + 从另一台设备迁移 + 设置密码短语 + 标准端到端加密 + 存档和上传 + 验证数据库密码短语 + 抗量子加密 + 应用数据迁移 + 通过二维码迁移到另一部设备。 + 画中画通话 + 更安全的群组 + 通话时使用本应用 + 迁移到此处 + 或粘贴存档链接 + 正在下载存档 + 无效链接 + 迁移中 + 粘贴存档链接 + 正在准备下载 + 下载失败了 + 重复下载 + %s 已下载 + 输入密码短语 + 导入失败了 + 正在导入存档 + 重复导入 + 已迁移聊天! + 下载存档出错 + 文件被删除或链接无效 + 在另一部设备上完成迁移 + 请确认网络设置对此这台设备正确无误。 + 应用 + 确认网络设置 + 导出聊天数据库出错 + 导出的文件不存在 + 迁移到另一部设备 + 正在准备上传 + 你的所有联系人、对话和文件将被安全加密并分块上传到配置的 XFTP 中继。 + 正在存档数据库 + 确认上传 + 删除数据库出错 + 必须停止聊天才能继续。 + 正在停止聊天 + 重复上传 + %s 已上传 + 上传失败了 + 正在上传存档 + 取消迁移 + 正在创建存档链接 + 完成迁移 + 从这部设备上删除数据库 + 或安全地分享此文件链接 + 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。 + 不能 在两部设备上使用同一数据库。]]> + 迁移完毕 + 启动聊天 + 请在迁移前确认你记得数据库的密码短语。 + 验证密码短语 + 警告:该存档将被删除。]]> + 检查你的互联网连接并重试 + 验证密码短语出错: + 端到端加密保护。]]> + 迁移设备 + 从一部设备迁移 并扫描二维码。]]> + 抗量子端到端加密保护。]]> + 管理员可以为所有人封禁一名成员。 + 正在下载链接详情 + 在私聊中开启(公测)! + 保存设置出错 + 上传存档出错 + 抗量子端到端加密 + 你可以再试一次。 + 你可以再试一次。 + 请注意: 作为安全保护措施,在两部设备上使用同一数据库会破坏解密来自你联系人的消息。]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt index 3f2e1a74ab..3fab849361 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt @@ -111,8 +111,12 @@ object NtfManager { } try { prevNtfs.add(chatId to builder.toast()) - } catch (e: Exception) { + } catch (e: Throwable) { Log.e(TAG, e.stackTraceToString()) + if (e !is Exception) { + val text = e.stackTraceToString().lines().getOrNull(0) ?: "" + showToast(generalGetString(MR.strings.error_showing_desktop_notification) + " " + text, 4_000) + } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 72aca181b3..71f862b30a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -32,9 +32,9 @@ fun initApp() { //testCrypto() } -fun discoverVlcLibs(path: String) { - uk.co.caprica.vlcj.binding.LibC.INSTANCE.setenv("VLC_PLUGIN_PATH", path, 1) -} +//fun discoverVlcLibs(path: String) { +// uk.co.caprica.vlcj.binding.LibC.INSTANCE.setenv("VLC_PLUGIN_PATH", path, 1) +//} private fun applyAppLocale() { val lang = ChatController.appPrefs.appLanguage.get() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt index 94268002a6..358c20d769 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt @@ -1,5 +1,6 @@ package chat.simplex.common.ui.theme +import androidx.compose.runtime.Composable import chat.simplex.common.platform.Log import chat.simplex.common.platform.TAG import com.jthemedetecor.OsThemeDetector @@ -9,6 +10,7 @@ private val detector: OsThemeDetector = OsThemeDetector.getDetector() registerListener(::reactOnDarkThemeChanges) } +@Composable actual fun isSystemInDarkTheme(): Boolean = try { detector.isDark } diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 4f5536a25b..c3dd9bb9b0 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -21,7 +21,7 @@ kotlin { dependencies { implementation(project(":common")) implementation(compose.desktop.currentOs) - implementation("net.java.dev.jna:jna:5.13.0") + implementation("net.java.dev.jna:jna:5.14.0") } } val jvmTest by getting diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 62f1ad21bc..1bfac36a28 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -25,12 +25,12 @@ android.nonTransitiveRClass=true android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 -android.version_name=5.6-beta.1 -android.version_code=190 +android.version_name=5.6 +android.version_code=191 -desktop.version_name=5.6-beta.1 -desktop.version_code=34 +desktop.version_name=5.6 +desktop.version_code=35 -kotlin.version=1.8.20 -gradle.plugin.version=7.4.2 -compose.version=1.5.10 +kotlin.version=1.9.23 +gradle.plugin.version=8.2.0 +compose.version=1.6.1 diff --git a/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties b/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties index 00c09d815c..4e4a6a3f29 100644 --- a/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties +++ b/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Feb 14 14:23:51 GMT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/simplex-chat/Main.hs b/apps/simplex-chat/Main.hs index f47bd6c7c4..41321edc68 100644 --- a/apps/simplex-chat/Main.hs +++ b/apps/simplex-chat/Main.hs @@ -1,61 +1,8 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE NamedFieldPuns #-} - module Main where -import Control.Concurrent (forkIO, threadDelay) -import Control.Concurrent.STM -import Control.Monad -import Data.Time.Clock (getCurrentTime) -import Data.Time.LocalTime (getCurrentTimeZone) -import Server -import Simplex.Chat.Controller (ChatController (..), ChatResponse (..), currentRemoteHost, versionNumber, versionString) -import Simplex.Chat.Core -import Simplex.Chat.Options -import Simplex.Chat.Terminal -import Simplex.Chat.View (serializeChatResponse) -import Simplex.Messaging.Client (NetworkConfig (..)) -import System.Directory (getAppUserDataDirectory) -import System.Terminal (withTerminal) +import Server (simplexChatServer) +import Simplex.Chat.Terminal (terminalChatConfig) +import Simplex.Chat.Terminal.Main (simplexChatCLI) main :: IO () -main = do - appDir <- getAppUserDataDirectory "simplex" - opts@ChatOpts {chatCmd, chatServerPort} <- getChatOpts appDir "simplex_v1" - if null chatCmd - then case chatServerPort of - Just chatPort -> simplexChatServer defaultChatServerConfig {chatPort} terminalChatConfig opts - _ -> runCLI opts - else simplexChatCore terminalChatConfig opts $ runCommand opts - where - runCLI opts = do - welcome opts - t <- withTerminal pure - simplexChatTerminal terminalChatConfig opts t - runCommand ChatOpts {chatCmd, chatCmdLog, chatCmdDelay} user cc = do - when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do - (_, _, r') <- atomically . readTBQueue $ outputQ cc - case r' of - CRNewChatItem {} -> printResponse r' - _ -> when (chatCmdLog == CCLAll) $ printResponse r' - sendChatCmdStr cc chatCmd >>= printResponse - threadDelay $ chatCmdDelay * 1000000 - where - printResponse r = do - ts <- getCurrentTime - tz <- getCurrentTimeZone - rh <- readTVarIO $ currentRemoteHost cc - putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r - -welcome :: ChatOpts -> IO () -welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} = - mapM_ - putStrLn - [ versionString versionNumber, - "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db", - maybe - "direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050" - (("using SOCKS5 proxy " <>) . show) - (socksProxy networkConfig), - "type \"/help\" or \"/h\" for usage info" - ] +main = simplexChatCLI terminalChatConfig (Just simplexChatServer) diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 3f4484eac6..58a167e4f5 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -28,9 +28,9 @@ import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception import UnliftIO.STM -simplexChatServer :: ChatServerConfig -> ChatConfig -> ChatOpts -> IO () -simplexChatServer srvCfg cfg opts = - simplexChatCore cfg opts . const $ runChatServer srvCfg +simplexChatServer :: ServiceName -> ChatConfig -> ChatOpts -> IO () +simplexChatServer chatPort cfg opts = + simplexChatCore cfg opts . const $ runChatServer defaultChatServerConfig {chatPort} data ChatServerConfig = ChatServerConfig { chatPort :: ServiceName, diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index a187ac3e82..1d7a866051 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -70,7 +70,7 @@ crDirectoryEvent = \case CRChatItemDeleted {deletedChatItem = AChatItem _ SMDRcv (DirectChat ct) _, byUser = False} -> Just $ DEItemDeleteIgnored ct CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} -> Just $ case (mc, itemLive) of - (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly directoryCmdP $ T.dropWhileEnd isSpace t + (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t _ -> DEUnsupportedMessage ct ciId where ciId = chatItemId' ci diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index ea79dabb10..c1428881b9 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -177,8 +177,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi badRolesMsg :: GroupRolesStatus -> Maybe String badRolesMsg = \case GRSOk -> Nothing - GRSServiceNotAdmin -> Just "You must have a group *owner* role to register the group" - GRSContactNotOwner -> Just "You must grant directory service *admin* role to register the group" + GRSServiceNotAdmin -> Just "You must grant directory service *admin* role to register the group" + GRSContactNotOwner -> Just "You must have a group *owner* role to register the group" GRSBadRoles -> Just "You must have a group *owner* role and you must grant directory service *admin* role to register the group" getGroupRolesStatus :: GroupInfo -> GroupReg -> IO (Maybe GroupRolesStatus) @@ -190,8 +190,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi groupRolesStatus :: GroupMemberRole -> GroupMemberRole -> GroupRolesStatus groupRolesStatus contactRole serviceRole = case (contactRole, serviceRole) of (GROwner, GRAdmin) -> GRSOk - (_, GRAdmin) -> GRSServiceNotAdmin - (GROwner, _) -> GRSContactNotOwner + (_, GRAdmin) -> GRSContactNotOwner + (GROwner, _) -> GRSServiceNotAdmin _ -> GRSBadRoles getGroupMember :: GroupReg -> IO (Maybe GroupMember) diff --git a/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md index ea4249f250..219dd2d5fe 100644 --- a/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md +++ b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md @@ -15,7 +15,7 @@ This is a major upgrade for SimpleX messaging protocols, we are really proud to This post also covers various aspects of end-to-end encryption, compares different messengers, and explains why and how quantum-resistant encryption is added to SimpleX Chat: - [Why do we need end-to-end encryption?](#why-do-we-need-end-to-end-encryption) -- [Why encryption is even allowed?](#why-encryption-is-even-allowed) +- [Why is encryption even allowed?](#why-is-encryption-even-allowed) - [End-to-end encryption security: attacks and defense.](#end-to-end-encryption-security-attacks-and-defense) - Compromised message size - mitigated by padding messages to a fixed block size. - Compromised confidentiality - mitigated by repudiation (deniability). @@ -40,7 +40,7 @@ End-to-end encryption is an important component of our individual and business s It's very sad to see the same people who keep their financial affairs private to protect from financial crimes, lock their doors to protect from thieves, and curtain their windows to protect from the occasional prying eyes, when it comes to protecting their personal lives from the data criminals say "we don't care about privacy, we have nothing to hide". Everybody's safety depends on keeping their affairs and relations private, not visible to a vast and ruthless data gathering machines, that abuse our data for commercial gain, without any regard to our interests or even [the safety of our families and children](https://nmdoj.gov/press-release/attorney-general-raul-torrez-files-lawsuit-against-meta-platforms-and-mark-zuckerberg-to-protect-children-from-sexual-abuse-and-human-trafficking/). -## Why encryption is even allowed? +## Why is encryption even allowed? @@ -75,7 +75,7 @@ While the content encryption is the most important, concealing the actual messag The only effective mitigation to these attacks is to pad all messages to a fixed size. Using space-efficient schemes like Padme, or padding to encryption block size is ineffective for mitigating these attacks, as they still allow differentiating message sizes. -To the best of our knowledge the only messenger other than SimpleX Chat that padded all messages to a fixed packet size was [Pond](https://github.com/agl/pond) - SimpleX design as an evolution of it. +To the best of our knowledge, the only messengers other than SimpleX Chat that pad all messages to a fixed packet size are Cwtch and no longer developed [Pond](https://github.com/agl/pond) - SimpleX design can be seen as an evolution of Pond design. ### 2. Compromised confidential messages - mitigated by repudiation (deniability) @@ -97,15 +97,15 @@ This property is well understood by the users, and most messengers that focus on ### 4. Compromised long-term or session - mitigated by break-in recovery -This attack is much less understood by the users, and forward secrecy does not protect from it. Arguably, it's almost impossible to compromise individual message keys without compromising long-term or session keys. So the ability of the encryption to recover from break-in (attacker making a copy of the device data without retaining the ongoing access) is both very and pragmatic - break-in attacks are simpler to execute on mobile devices during short-term device access than long-term ongoing compromise. +This attack is much less understood by the users, and forward secrecy does not protect from it. Arguably, it's almost impossible to compromise individual message keys without compromising long-term or session keys. So the ability of the encryption to recover from break-in (attacker making a copy of the device data without retaining the ongoing access) is both very important and pragmatic - break-in attacks are simpler to execute on mobile devices during short-term device access than long-term ongoing compromise. -Out of all encryption algorithms known to us only Signal double ratchet algorithm provides the ability to encryption security after break-ins. This recovery happens automatically and transparently to the users, without them doing anything special even knowing about break-in, by simply sending messages. Every time one of the communication parties replies to another party message, new random keys are generated and previously stolen keys become useless. +Out of all encryption algorithms known to us only _Signal double ratchet algorithm_ (also referred to as _Signal algorithm_ or _double ratchet algorithm_, which is not the same as Signal messaging platform and protocols) provides the ability for the encryption security to recover after break-ins attacks. This recovery happens automatically and transparently to the users, without them doing anything special or even knowing about break-in, by simply sending messages. Every time one of the communication parties replies to another party message, new random keys are generated and previously stolen keys become useless. -Signal double ratchet algorithm is used in Signal, Cwtch and SimpleX Chat. This is why you cannot use SimpleX Chat profile on more than one device at the same time - the encryption scheme rotates the long term keys, randomly, and keys on another device become useless, as they would become useless for the attacker who stole them. Security always has some costs to the convenience. +Double ratchet algorithm is used in Signal, Cwtch and SimpleX Chat. This is why you cannot use SimpleX Chat profile on more than one device at the same time - the encryption scheme rotates the long term keys, randomly, and keys on another device become useless, as they would become useless for the attacker who stole them. Security always has some costs to the convenience. ### 5. Man-in-the-middle attack - mitigated by two-factor key exchange -Many people incorrectly believe that security of end-to-end encryption cannot be broken by communication provider. But end-to-end encryption is as secure as key exchange. While any intermediary passing the keys between senders and recipients cannot recover the private keys from the public keys, they can simply replace the passed public keys with their own and then proxy all communication between the users having full access to the original messages. So instead of having an end-to-end encrypted channel, users would have two half-way encrypted channels - between users and their communication intermediary. +Many people incorrectly believe that security of end-to-end encryption cannot be broken by communication provider. But end-to-end encryption is only as secure as key exchange. While any intermediary passing the keys between senders and recipients cannot recover the private keys from the public keys, they can simply replace the passed public keys with their own and then proxy all communication between the users having full access to the original messages. So instead of having an end-to-end encrypted channel, users would have two half-way encrypted channels - between users and their communication intermediary. Pictures below illustrate how this attack works for RSA encryption. @@ -143,7 +143,7 @@ Post-quantum cryptography, or encryption algorithms that are resistant to quantu - many of post-quantum algorithms have known patent claims, so any system deploying them accepts the risks of patent litigation. - the silver lining to these limitations is that the risk of appearance of commercially viable quantum computers in the next decade may be exaggerated. -So, to put it bluntly and provocatively, post-quantum cryptography can be compared with a remedy against the illness that nobody has, without any guarantee that it will work. The closest analogy in the history of medicine is _snake oil_. +So, to say it provocatively, post-quantum cryptography can be compared with a remedy against the illness that nobody has, without any guarantee that it will work. While there is a reasonable hope that it _might_ work, so it's not exactly a _snake oil_, these limitations and risks have to be much better communicated to the end users than they are. @@ -164,8 +164,8 @@ The main objective here is to establish the framework for comparing the security Some columns are marked with a yellow checkmark: - when messages are padded, but not to a fixed size. - when repudiation does not include client-server connection. In case of Cwtch it appears that the presence of cryptographic signatures compromises repudiation (deniability), but it needs to be clarified. -- when 2-factor key exchange is optional, via security code verification. -- when post-quantum cryptography is only added to the initial key agreement, does not protect break-in recovery. +- when 2-factor key exchange is optional (via security code verification). +- when post-quantum cryptography is only added to the initial key agreement and does not protect break-in recovery. ## Adding quantum resistance to Signal double ratchet algorithm @@ -201,7 +201,7 @@ The reason it is released as opt-in is because once the conversation is upgraded - enable _Show developer options_ toggle. - now you will see _Post-quantum E2EE_ toggle - enable it as well. -Now all new contacts you add to the app will use quantum resistant Signal double ratchet algorithm. +Now all new contacts you add to the app will use quantum resistant double ratchet algorithm. Once you have enabled it for the new contacts, you can also **enable it for some of the existing contacts**: - open the chat with the contact you want to upgrade to be quantum resistant. @@ -211,7 +211,7 @@ Once you have enabled it for the new contacts, you can also **enable it for some ## Next for post-quantum crypto - all direct chats, small groups and security audit -We will be making quantum resistance default for all direct chats in v5.7, and they will be upgraded for all users without any action. +We will be making quantum resistance default for all direct chats in v5.7, and we plan that all existing direct chats will be automatically upgraded when both contacts install v5.7. We will also be adding quantum resistance to small groups up to 10-20 members. Computing cryptographic keys is much slower, in comparison, and it would be very inefficient (and completely unnecessary) for large public groups. diff --git a/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md index f036b95f1f..c017b9d1cc 100644 --- a/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md +++ b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md @@ -1,20 +1,128 @@ --- layout: layouts/article.html -title: "SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration" +title: "SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration" date: 2024-03-23 -# previewBody: blog_previews/20240314.html -preview: TODO -draft: true -# image: images/20240314-kem.jpg -# imageWide: true +previewBody: blog_previews/20240323.html +image: images/20240323-post-preview.png +imageBottom: true permalink: "/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html" --- -# SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration +# SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration -This is a stub for release permalink +**Published:** Mar 23, 2024 -TODO +SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance: +- [community and business interests are aligned](#community-and-business-interests-are-aligned). +- [the journey to the decentralized non-profit protocol governance](#the-journey-to-the-decentralized-non-profit-protocol-governance). +- [welcome, Esra’a](#welcome-esraa)! + +What's new in v5.6: +- [quantum resistant end-to-end encryption](#quantum-resistant-end-to-end-encryption-beta) (BETA) – enable it for the new contacts. +- [use the app during the audio and video calls](#use-the-app-during-the-audio-and-video-calls). +- [migrate all app data to another device via QR code](#migrate-all-app-data-to-another-device-via-qr-code). + +There are many other improvements and fixes in this release: +- group admins can block a member for all other members. +- filtering chats no longer includes muted chats with unread messages. +- and more - see the [release notes](https://github.com/simplex-chat/simplex-chat/releases/tag/v5.6.0). + +## SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance + +### Community and business interests are aligned + +Some people in our users' community believe that there is a conflict between the business and the community interests. This view fails to see a bigger picture of how these interests are aligned. I wrote a blog post about it [here](https://www.poberezkin.com/posts/2023-10-31-why-privacy-impossible-without-venture-funding.html). Our goal is to grow the network and the ecosystem while enriching all stakeholders involved, both the community and the business shareholders. This strategy relies on the strengths and opportunities of both the business and the decentralized network we build, with its vibrant community of individuals and organizations. It positions the business not as a controller but as a supporter of the community growth, resilience and sustainability. + +By leveraging access to investment resources, its ability to provide stock-based incentives beyond salaries, operating as a business will satisfy users' and community needs more effectively, ensuring the long-term viability and growth of the network, while remaining true to the mission. A purely nonprofit model with no avenue to sustain this growth is simply not possible. Sustainability requires profits, that is simply spending less than earning, as every responsible individual and organization should aim for, and these profits must be based on users' payments, and not on some other sources of revenue or funds. And building the software product that customers are willing to pay for requires substantial business investments. We will accomplish it while maintaining transparency and the commitment to keep SimpleX network and its protocols open and free to use. + +### The journey to the decentralized non-profit protocol governance + +At the current early stage of network growth the protocols will benefit most from the rapid evolution and centralized design. Many protocols that decentralized their evolution too early failed to evolve and to adapt to the product requirements of the majority of users, causing them to stall in growth. Even the open web became possible only thanks to Netscape, a venture funded company, that developed browsers and web server software taking it upon themselves to rapidly evolve web specification to make it into a robust application platform as we now know it. It is very unlikely that it would have happened if it was managed by a nonprofit or community, in a decentralized way. + +Once SimpleX network protocols stabilize and reach the required maturity, we plan to transition its governance to several new nonprofit entities, ensuring its continued evolution aligns more closely with our vision of community-driven, independent and transparent governance. We and our investors believe that this is a win-win strategy. It both maximizes the business value, by creating a product built on protecting privacy and users' trust - a model that we see as the only viable path forward. It also maximizes the value created for the user community. + +### Welcome, Esra’a! + +To help us deliver these goals, Esra’a Al Shafei joined SimpleX Chat team - we are excited to welcome her! + +Esra'a is a longtime privacy and human rights advocate from Bahrain. She is the founder of [Majal.org](http://majal.org), a network of platforms that amplify under-reported and marginalized voices in the Middle East and North Africa. She is also the co-founder of the [Numun Fund](https://numun.fund/), the first dedicated fund for women-led tech in the Global South. Esra’a is currently the Vice Chair of the Board at the [Wikimedia Foundation](https://wikimediafoundation.org/), the nonprofit which hosts Wikipedia. She is also on the Board of the [Tor Project](https://www.torproject.org/), developers of one of the world’s strongest tools for privacy and freedom online. Previously, she served on the Board of [Access Now](https://www.accessnow.org/), an international nonprofit dedicated to an open and free Internet. + +In her own words: *"Privacy and security have been the center of my work as a human rights advocate for the last 20 years in one of the most surveilled countries in the world where staying safe remains a burden. Our privacy is our dignity. It is something that belongs to us and something we should treasure - as people, as companies, as governments. Despite messaging being the most intimate aspect of our digital lives, the leading protocols in this space are centralized and we deserve a web that operates on the core principles of genuine privacy and security in a fully decentralized framework. SimpleX network will play a critical role towards that vision."* + +Executing the plan towards decentralized nonprofit protocol governance will be Esra’a’s primary responsibility. Our primary objective with this plan is to ensure that no single entity can control or have unrestricted ownership of the protocols, so it can maintain its integrity. + +## What's new in v5.6 + +### Quantum resistant end-to-end encryption (BETA) + +Adding post-quantum resistance to the double ratchet end-to-end encryption algorithm in SimpleX Chat is a major upgrade to messaging protocols, and it creates the security against the future quantum computers. + +I wrote in detail in the previous post about various properties of end-to-end encryption and how exactly quantum resistance is implemented in SimpleX Chat - currently SimpleX Chat has [the most secure end-to-end encryption design](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md#how-secure-is-end-to-end-encryption-in-different-messengers) against the present and future attacks. + +We plan to perform a security audit of this design and implementation later this year. + +Post-quantum encryption is currently added as opt-in and has to be separately enabled to be active for the new contacts, and, additionally, can be enabled for the existing contacts. + + + +**To enable quantum resistance for the new conversations**: +- open the app settings (tap user avatar in the top left corner). +- scroll down to _Developer tools_ and open them. +- enable _Show developer options_ toggle. +- now you will see _Post-quantum E2EE_ toggle - enable it as well. + +Now all new contacts you add to the app will use quantum resistant Signal double ratchet algorithm. + +Once you have enabled it for the new contacts, you can also **enable it for some of the existing contacts**: +- open the chat with the contact you want to upgrade to be quantum resistant. +- tap contact name above the chat. +- tap Allow PQ encryption. +- exchange several messages back and forth with that contact - the quantum resistant double ratchet will kick in after 3-5 messages (depending on how many messages you send in each direction), and you will see the notice in the chat once it enables. + +We will be making quantum resistance default for all direct chats in v5.7, and we plan that all existing direct chats will be automatically upgraded when both contacts install v5.7. + +### Use the app during the audio and video calls + + + +Since we added the audio and video calls in 2022 a lot of work was done to improve the stability of calls. One of the usability downsides until this release was that it was impossible to use the app during the call. + +This version solved this problem - now you can return back to the app without interrupting the call and use any of the app functions - for example, you can send the messages or files to the person you are in the call with. If this is a video call, you will continue seeing your contact in a small picture-in-picture view. + +### Migrate all app data to another device via QR code + +We always believed that cross-platform data portability is very important for any software. Users own their data, and they should have a way to export it from any software they use. So from the version of SimpleX Chat [released in July 2022](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#database-export-and-import) it was possible to export the database as a self-contained file, including all contacts, messages and files, and import it on a new device. + +_"How can I migrate my data to another device?"_ was one of the most frequent user questions, but until this release this process was [quite complex](), requiring too many steps, and most non-technical users were not willing to try it, as even though it was reliable, it appeared risky. + +This release made the app data migration very simple, see the steps below. + +**Start the migration on the source device** + + + +On the device you migrate your app data from, follow these steps: +- choose _Migrate to another device_, +- if necessary, set the database passphrase (initially, the database is encrypted with a random passphrase), or, if it's already set, verify it - for security and to ensure that the archive will be usable on the new device, +- tap _Archive and upload_ to and upload the app data in chunks to the XFTP servers configured in the app, +- the app will show the QR code that should be scanned from the new device. + +**Migrate data to the target device** + + + +On the device you migrate your data to, follow these steps: +- on the first app page choose _Migrate from another device_, +- scan the QR code shown on the source device, +- once the archive downloads, enter the database passphrase and tap _Open chat_, + +**Finalize migration on the source device** + + + +When the chat starts on the target device, make sure to tap _Finalize migration_ and then _Delete database_ on the source device. + +The database cannot be used on two devices at the same time, doing so would make it impossible to decrypt the received messages. This is a security measure - the break-in recovery property of double ratchet algorithm. ## SimpleX network diff --git a/blog/README.md b/blog/README.md index 3b89628211..7f27c46c76 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,19 @@ # Blog +Mar 23, 2024 [SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration](./20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md) + +SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance: +- community and business interests are aligned. +- the journey to the decentralized non-profit protocol governance. +- welcome, Esra’a! + +What's new in v5.6: +- quantum resistant end-to-end encryption (BETA) – enable it for the new contacts. +- use the app during the audio and video calls. +- migrate all app data to another device via QR code. + +--- + Mar 14, 2024 [SimpleX Chat v5.6 (beta): adding quantum resistance to Signal double ratchet algorithm](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) This is a major upgrade for SimpleX Chat messaging protocol stack, I am really proud to present this work of the whole team. diff --git a/blog/images/20240314-comparison.jpg b/blog/images/20240314-comparison.jpg index 34027a43a7..579b2fd73c 100644 Binary files a/blog/images/20240314-comparison.jpg and b/blog/images/20240314-comparison.jpg differ diff --git a/blog/images/20240323-call1.png b/blog/images/20240323-call1.png new file mode 100644 index 0000000000..0cf954a07e Binary files /dev/null and b/blog/images/20240323-call1.png differ diff --git a/blog/images/20240323-call2.png b/blog/images/20240323-call2.png new file mode 100644 index 0000000000..8a69d1891e Binary files /dev/null and b/blog/images/20240323-call2.png differ diff --git a/blog/images/20240323-migrate-from0.png b/blog/images/20240323-migrate-from0.png new file mode 100644 index 0000000000..033ead3fd9 Binary files /dev/null and b/blog/images/20240323-migrate-from0.png differ diff --git a/blog/images/20240323-migrate-from1.png b/blog/images/20240323-migrate-from1.png new file mode 100644 index 0000000000..be016ada2c Binary files /dev/null and b/blog/images/20240323-migrate-from1.png differ diff --git a/blog/images/20240323-migrate-from2.png b/blog/images/20240323-migrate-from2.png new file mode 100644 index 0000000000..2cee12e6f4 Binary files /dev/null and b/blog/images/20240323-migrate-from2.png differ diff --git a/blog/images/20240323-migrate-from3.png b/blog/images/20240323-migrate-from3.png new file mode 100644 index 0000000000..92d7bf19a6 Binary files /dev/null and b/blog/images/20240323-migrate-from3.png differ diff --git a/blog/images/20240323-migrate-from4.png b/blog/images/20240323-migrate-from4.png new file mode 100644 index 0000000000..9b96f9efd8 Binary files /dev/null and b/blog/images/20240323-migrate-from4.png differ diff --git a/blog/images/20240323-migrate-from5.png b/blog/images/20240323-migrate-from5.png new file mode 100644 index 0000000000..dfc22a3987 Binary files /dev/null and b/blog/images/20240323-migrate-from5.png differ diff --git a/blog/images/20240323-migrate-from6.png b/blog/images/20240323-migrate-from6.png new file mode 100644 index 0000000000..18d9c8b627 Binary files /dev/null and b/blog/images/20240323-migrate-from6.png differ diff --git a/blog/images/20240323-migrate-to1.png b/blog/images/20240323-migrate-to1.png new file mode 100644 index 0000000000..b3fb0e32f1 Binary files /dev/null and b/blog/images/20240323-migrate-to1.png differ diff --git a/blog/images/20240323-migrate-to2.png b/blog/images/20240323-migrate-to2.png new file mode 100644 index 0000000000..f64544cd9b Binary files /dev/null and b/blog/images/20240323-migrate-to2.png differ diff --git a/blog/images/20240323-migrate-to3.png b/blog/images/20240323-migrate-to3.png new file mode 100644 index 0000000000..626032737b Binary files /dev/null and b/blog/images/20240323-migrate-to3.png differ diff --git a/blog/images/20240323-migrate-to4.png b/blog/images/20240323-migrate-to4.png new file mode 100644 index 0000000000..59153a464c Binary files /dev/null and b/blog/images/20240323-migrate-to4.png differ diff --git a/blog/images/20240323-migrate-to5.png b/blog/images/20240323-migrate-to5.png new file mode 100644 index 0000000000..b86f42b292 Binary files /dev/null and b/blog/images/20240323-migrate-to5.png differ diff --git a/blog/images/20240323-post-preview.png b/blog/images/20240323-post-preview.png new file mode 100644 index 0000000000..69c9908a1e Binary files /dev/null and b/blog/images/20240323-post-preview.png differ diff --git a/cabal.project b/cabal.project index be58b64283..12879f4c76 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 8496884b42d76bd0d934e5ae481846d9c324d3c7 + tag: bfd532e833aff36754ef766f4e021f0079a7f83c source-repository-package type: git @@ -34,6 +34,12 @@ source-repository-package location: https://github.com/simplex-chat/aeson.git tag: aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b +-- old bs/text compat for 8.10 +source-repository-package + type: git + location: https://github.com/simplex-chat/base64.git + tag: 2d77b6dbcaffc00570a70be8694049f3710e7c94 + source-repository-package type: git location: https://github.com/simplex-chat/haskell-terminal.git diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index 6726fa02fe..b394c9dd27 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -4,10 +4,10 @@ permalink: /downloads/index.html revision: 11.02.2024 --- -| Updated 11.02.2024 | Languages: EN | +| Updated 23.03.2024 | Languages: EN | # Download SimpleX apps -The latest stable version is v5.5.3. +The latest stable version is v5.6. You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases). diff --git a/docs/rfcs/2024-03-22-communicating-reject.md b/docs/rfcs/2024-03-22-communicating-reject.md new file mode 100644 index 0000000000..37bb4eab26 --- /dev/null +++ b/docs/rfcs/2024-03-22-communicating-reject.md @@ -0,0 +1,95 @@ +# Communicating rejection + +## Problem + +Many interactions support either approval, or silent rejection. From privacy perspective, rejection being silent is a correct default. However, for improved usability we could add optional communication of rejection, as many users don't mind sending such signals to their contacts. + +Features currently not supporting communicating rejection: +- Rejecting contact request +- Rejecting group join request +- Rejecting call +- Rejecting group invitation +- TBC Other? + +## Solution + +## Rejection of contact / group join requests + +- Contact and group join requests are very similar between each other and different from other features as they both use mechanism of "contact connections". + +- Rejection can be made to the address proposed by requester in AgentInvitation message, where currently AgentConfirmation is sent in case of acceptance. + + ``` haskell + data AgentMsgEnvelope + = ... + | AgentRejection + { e2eEncryption :: RcvE2ERatchetParams 'C.X448, + encRejectionInfo :: ByteString + } + ``` + +- Unlike other AgentMsgEnvelope constructors, AgentRejection doesn't require agentVersion since connection will be deleted after sending this message. + + - We may be able to re-use recently added mechanism of marking connection for deletion with deleted_at_wait_delivery field without much additional work. + +- e2eEncryption will be used to encrypt first message, same as for AgentConfirmation. (?) + +- Both sync and async versions of agent functions are required, as contact rejection will be user action, while group join rejection will be automated (in case, for example, if link host is no longer admin). + + - Group requests non-automatic approval is a separate matter and requires UI consideration, but if it was added it would use sync function. + + - For sync function either new API can be added, or rejectContact can be parameterized. + +- Chat protocol requires adding new messages to be sent in encRejectionInfo, to be processed on requester side based on connections' semantics. + + ```haskell + -- / contact request rejection + XReject :: ChatMsgEvent 'Json + -- or + -- (contact can send reason for rejection; we don't even have welcome messages in XContact though) + XReject :: Maybe Text -> ChatMsgEvent 'Json + + -- / group join request rejection + XGrpReject :: Maybe GrpRejectReason -> ChatMsgEvent 'Json + + data GrpRejectReason + = GRRNone -- manual reject with no reason? use Nothing in GRRText instead? + | GRRCantInvite -- e.g., no longer admin + | GRRText {text :: Text} -- manual reject once supported? Maybe Text? + + -- add similar type for contact request rejection? + -- minimal is to always send XReject without reasons + ``` + + - As a side note, it may have been a design mistake to mix both "connInfo" messages as well as regular chat messages in a single type for protocol messages, so it may be best to keep these as separate constructors. + +- Versioning considerations: + - Increase chat version. + - We already save peer chat version on contact_requests on initial REQ message. It can be used to differentiate UI whether contact supports rejection messages and not offer option to reject with notification. + - Increase agent version? Agent can prohibit sending AgentRejection based on version in AgentInvitation, though it shouldn't be reachable as chat should also prohibit it. + +## Rejecting calls + +- New chat protocol message is enough + - Based on user action in reply to XCallInv instead of XCallOffer. + - Send in APIRejectCall. + + ```haskell + XCallReject :: CallId -> ChatMsgEvent 'Json + ``` + +- Same chat versioning considerations as above. + +## Rejecting group invitation + +- Same, new protocol message. + - Based on user action in reply to XGrpInv instead of XGrpAcpt. + - Can't be sent as simple chat message since there's no group ID in invitation? (only optional groupLinkId) + - So, have to send as "conn info" via join, same as for XGrpAcpt. + - APIDeleteChat is already used for deleting group invitations, can re-use. In this case `notify` parameter in APIDeleteChat can be used to send rejection. + + ```haskell + XGrpReject :: CallId -> ChatMsgEvent 'Json + ``` + +- Same chat versioning considerations as above. diff --git a/package.yaml b/package.yaml index 7d8af47401..1f891df76b 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.6.0.4 +version: 5.6.1.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme @@ -18,7 +18,6 @@ dependencies: - async == 2.2.* - attoparsec == 0.14.* - base >= 4.7 && < 5 - - base64-bytestring >= 1.0 && < 1.3 - composition == 1.0.* - constraints >= 0.12 && < 0.14 - containers == 0.6.* @@ -104,16 +103,20 @@ executables: - -threaded simplex-broadcast-bot: - source-dirs: apps/simplex-broadcast-bot/src - main: ../Main.hs + source-dirs: + - apps/simplex-broadcast-bot + - apps/simplex-broadcast-bot/src + main: Main.hs dependencies: - simplex-chat ghc-options: - -threaded simplex-directory-service: - source-dirs: apps/simplex-directory-service/src - main: ../Main.hs + source-dirs: + - apps/simplex-directory-service + - apps/simplex-directory-service/src + main: Main.hs dependencies: - simplex-chat ghc-options: @@ -147,6 +150,7 @@ tests: ghc-options: # - -haddock + - -O2 - -Wall - -Wcompat - -Werror=incomplete-patterns @@ -154,3 +158,6 @@ ghc-options: - -Wincomplete-record-updates - -Wincomplete-uni-patterns - -Wunused-type-patterns + +default-extensions: + - StrictData diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 1fd783a1d3..9942f3f4a5 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,9 +1,10 @@ { - "https://github.com/simplex-chat/simplexmq.git"."8496884b42d76bd0d934e5ae481846d9c324d3c7" = "0c040s00zc1qg0ilsvpv54f01xz7flh8rsar2nplcs4a4gnp78qs"; + "https://github.com/simplex-chat/simplexmq.git"."bfd532e833aff36754ef766f4e021f0079a7f83c" = "1xxcdadllimk2hgzz6aggvywr14zm2h0l62c92yvnyvps9j49gdx"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr"; + "https://github.com/simplex-chat/base64.git"."2d77b6dbcaffc00570a70be8694049f3710e7c94" = "0zdskk67fzqrrx1i29s3shp7fh9c0krmq5h6hq03qx0n3xy2m44b"; "https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj"; "https://github.com/simplex-chat/android-support.git"."9aa09f148089d6752ce563b14c2df1895718d806" = "0pbf2pf13v2kjzi397nr13f1h3jv0imvsq8rpiyy2qyx5vd50pqn"; "https://github.com/simplex-chat/zip.git"."bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc" = "1csqfjhvc8wb5h4kxxndmb6iw7b4ib9ff2n81hrizsmnf45a6gg0"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 9caa46da55..3ab530e2d9 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.6.0.4 +version: 5.6.1.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat @@ -138,6 +138,8 @@ library Simplex.Chat.Migrations.M20240222_app_settings Simplex.Chat.Migrations.M20240226_users_restrict Simplex.Chat.Migrations.M20240228_pq + Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id + Simplex.Chat.Migrations.M20240324_custom_data Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared @@ -167,6 +169,7 @@ library Simplex.Chat.Styled Simplex.Chat.Terminal Simplex.Chat.Terminal.Input + Simplex.Chat.Terminal.Main Simplex.Chat.Terminal.Notification Simplex.Chat.Terminal.Output Simplex.Chat.Types @@ -178,14 +181,15 @@ library Paths_simplex_chat hs-source-dirs: src - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns + default-extensions: + StrictData + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -238,14 +242,15 @@ executable simplex-bot Paths_simplex_chat hs-source-dirs: apps/simplex-bot - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + default-extensions: + StrictData + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -299,14 +304,15 @@ executable simplex-bot-advanced Paths_simplex_chat hs-source-dirs: apps/simplex-bot-advanced - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + default-extensions: + StrictData + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -355,21 +361,23 @@ executable simplex-bot-advanced , text >=1.2.3.0 && <1.3 executable simplex-broadcast-bot - main-is: ../Main.hs + main-is: Main.hs + hs-source-dirs: + apps/simplex-broadcast-bot + apps/simplex-broadcast-bot/src + default-extensions: + StrictData other-modules: Broadcast.Bot Broadcast.Options Paths_simplex_chat - hs-source-dirs: - apps/simplex-broadcast-bot/src - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -424,14 +432,15 @@ executable simplex-chat Paths_simplex_chat hs-source-dirs: apps/simplex-chat - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + default-extensions: + StrictData + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -481,7 +490,12 @@ executable simplex-chat , text >=1.2.3.0 && <1.3 executable simplex-directory-service - main-is: ../Main.hs + main-is: Main.hs + hs-source-dirs: + apps/simplex-directory-service + apps/simplex-directory-service/src + default-extensions: + StrictData other-modules: Directory.Events Directory.Options @@ -489,16 +503,13 @@ executable simplex-directory-service Directory.Service Directory.Store Paths_simplex_chat - hs-source-dirs: - apps/simplex-directory-service/src - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* @@ -583,7 +594,9 @@ test-suite simplex-chat-test tests apps/simplex-broadcast-bot/src apps/simplex-directory-service/src - ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + default-extensions: + StrictData + ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded build-depends: QuickCheck ==2.14.* , aeson ==2.2.* @@ -591,7 +604,6 @@ test-suite simplex-chat-test , async ==2.2.* , attoparsec ==0.14.* , base >=4.7 && <5 - , base64-bytestring >=1.0 && <1.3 , composition ==1.0.* , constraints >=0.12 && <0.14 , containers ==0.6.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index cdb6f90a98..2367294489 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -29,7 +29,6 @@ import qualified Data.Attoparsec.ByteString.Char8 as A import Data.Bifunctor (bimap, first, second) import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteArray as BA -import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB @@ -104,8 +103,9 @@ import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKNoPQ, pattern IKPQOff, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding +import Simplex.Messaging.Encoding.Base64 (base64P) +import qualified Simplex.Messaging.Encoding.Base64 as B64 import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (base64P) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) @@ -132,7 +132,7 @@ defaultChatConfig = ChatConfig { agentConfig = defaultAgentConfig - { tcpPort = undefined, -- agent does not listen to TCP + { tcpPort = Nothing, -- agent does not listen to TCP tbqSize = 1024 }, chatVRange = supportedChatVRange, @@ -161,7 +161,8 @@ defaultChatConfig = ciExpirationInterval = 30 * 60 * 1000000, -- 30 minutes coreApi = False, highlyAvailable = False, - deviceNameForRemote = "" + deviceNameForRemote = "", + chatHooks = defaultChatHooks } _defaultSMPServers :: NonEmpty SMPServerWithAuth @@ -321,11 +322,10 @@ cfgServers p DefaultAgentServers {smp, xftp} = case p of SPSMP -> smp SPXFTP -> xftp -startChatController :: forall m. ChatMonad' m => Bool -> m (Async ()) +startChatController :: Bool -> CM' (Async ()) startChatController mainApp = do - asks smpAgent >>= resumeAgentClient - unless mainApp $ - chatWriteVar subscriptionMode SMOnlyCreate + asks smpAgent >>= liftIO . resumeAgentClient + unless mainApp $ chatWriteVar' subscriptionMode SMOnlyCreate users <- fromRight [] <$> runExceptT (withStore' getUsers) restoreCalls s <- asks agentAsync @@ -363,43 +363,43 @@ startChatController mainApp = do startExpireCIThread user setExpireCIFlag user True -subscribeUsers :: forall m. ChatMonad' m => Bool -> [User] -> m () +subscribeUsers :: Bool -> [User] -> CM' () subscribeUsers onlyNeeded users = do let (us, us') = partition activeUser users - vr <- chatVersionRange + vr <- chatVersionRange' subscribe vr us subscribe vr us' where - subscribe :: (PQSupport -> VersionRangeChat) -> [User] -> m () + subscribe :: (PQSupport -> VersionRangeChat) -> [User] -> CM' () subscribe vr = mapM_ $ runExceptT . subscribeUserConnections vr onlyNeeded Agent.subscribeConnections -startFilesToReceive :: forall m. ChatMonad' m => [User] -> m () +startFilesToReceive :: [User] -> CM' () startFilesToReceive users = do let (us, us') = partition activeUser users startReceive us startReceive us' where - startReceive :: [User] -> m () + startReceive :: [User] -> CM' () startReceive = mapM_ $ runExceptT . startReceiveUserFiles -startReceiveUserFiles :: ChatMonad m => User -> m () +startReceiveUserFiles :: User -> CM () startReceiveUserFiles user = do filesToReceive <- withStore' (`getRcvFilesToReceive` user) forM_ filesToReceive $ \ft -> flip catchChatError (toView . CRChatError (Just user)) $ toView =<< receiveFile' user ft Nothing Nothing -restoreCalls :: ChatMonad' m => m () +restoreCalls :: CM' () restoreCalls = do savedCalls <- fromRight [] <$> runExceptT (withStore' getCalls) let callsMap = M.fromList $ map (\call@Call {contactId} -> (contactId, call)) savedCalls calls <- asks currentCalls atomically $ writeTVar calls callsMap -stopChatController :: forall m. MonadUnliftIO m => ChatController -> m () +stopChatController :: ChatController -> IO () stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, expireCIFlags, remoteHostSessions, remoteCtrlSession} = do - readTVarIO remoteHostSessions >>= mapM_ (liftIO . cancelRemoteHost False . snd) - atomically (stateTVar remoteCtrlSession (,Nothing)) >>= mapM_ (liftIO . cancelRemoteCtrl False . snd) + readTVarIO remoteHostSessions >>= mapM_ (cancelRemoteHost False . snd) + atomically (stateTVar remoteCtrlSession (,Nothing)) >>= mapM_ (cancelRemoteCtrl False . snd) disconnectAgentClient smpAgent readTVarIO s >>= mapM_ (\(a1, a2) -> uninterruptibleCancel a1 >> mapM_ uninterruptibleCancel a2) closeFiles sndFiles @@ -409,13 +409,13 @@ stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, forM_ keys $ \k -> TM.insert k False expireCIFlags writeTVar s Nothing where - closeFiles :: TVar (Map Int64 Handle) -> m () + closeFiles :: TVar (Map Int64 Handle) -> IO () closeFiles files = do fs <- readTVarIO files mapM_ hClose fs atomically $ writeTVar files M.empty -execChatCommand :: ChatMonad' m => Maybe RemoteHostId -> ByteString -> m ChatResponse +execChatCommand :: Maybe RemoteHostId -> ByteString -> CM' ChatResponse execChatCommand rh s = do u <- readTVarIO =<< asks currentUser case parseChatCommand s of @@ -424,18 +424,20 @@ execChatCommand rh s = do Just rhId | allowRemoteCommand cmd -> execRemoteCommand u rhId cmd s | otherwise -> pure $ CRChatCmdError u $ ChatErrorRemoteHost (RHId rhId) $ RHELocalCommand - _ -> execChatCommand_ u cmd + _ -> do + cc@ChatController {config = ChatConfig {chatHooks}} <- ask + liftIO (preCmdHook chatHooks cc cmd) >>= either pure (execChatCommand_ u) -execChatCommand' :: ChatMonad' m => ChatCommand -> m ChatResponse +execChatCommand' :: ChatCommand -> CM' ChatResponse execChatCommand' cmd = asks currentUser >>= readTVarIO >>= (`execChatCommand_` cmd) -execChatCommand_ :: ChatMonad' m => Maybe User -> ChatCommand -> m ChatResponse +execChatCommand_ :: Maybe User -> ChatCommand -> CM' ChatResponse execChatCommand_ u cmd = handleCommandError u $ processChatCommand cmd -execRemoteCommand :: ChatMonad' m => Maybe User -> RemoteHostId -> ChatCommand -> ByteString -> m ChatResponse +execRemoteCommand :: Maybe User -> RemoteHostId -> ChatCommand -> ByteString -> CM' ChatResponse execRemoteCommand u rhId cmd s = handleCommandError u $ getRemoteHostClient rhId >>= \rh -> processRemoteCommand rhId rh cmd s -handleCommandError :: ChatMonad' m => Maybe User -> ExceptT ChatError m ChatResponse -> m ChatResponse +handleCommandError :: Maybe User -> CM ChatResponse -> CM' ChatResponse handleCommandError u a = either (CRChatCmdError u) id <$> (runExceptT a `E.catches` ioErrors) where ioErrors = @@ -447,12 +449,12 @@ parseChatCommand :: ByteString -> Either String ChatCommand parseChatCommand = A.parseOnly chatCommandP . B.dropWhileEnd isSpace -- | Chat API commands interpreted in context of a local zone -processChatCommand :: forall m. ChatMonad m => ChatCommand -> m ChatResponse +processChatCommand :: ChatCommand -> CM ChatResponse processChatCommand cmd = chatVersionRange >>= (`processChatCommand'` cmd) {-# INLINE processChatCommand #-} -processChatCommand' :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> ChatCommand -> m ChatResponse +processChatCommand' :: (PQSupport -> VersionRangeChat) -> ChatCommand -> CM ChatResponse processChatCommand' vr = \case ShowActiveUser -> withUser' $ pure . CRActiveUser CreateActiveUser NewUser {profile, sameServers, pastTimestamp} -> do @@ -475,7 +477,7 @@ processChatCommand' vr = \case atomically . writeTVar u $ Just user pure $ CRActiveUser user where - chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> m (NonEmpty (ProtoServerWithAuth p), [ServerCfg p]) + chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> CM (NonEmpty (ProtoServerWithAuth p), [ServerCfg p]) chooseServers protocol | sameServers = asks currentUser >>= readTVarIO >>= \case @@ -494,7 +496,7 @@ processChatCommand' vr = \case day = 86400 ListUsers -> CRUsersList <$> withStore' getUsersInfo APISetActiveUser userId' viewPwd_ -> do - unlessM chatStarted $ throwChatError CEChatNotStarted + unlessM (lift chatStarted) $ throwChatError CEChatNotStarted user_ <- chatReadVar currentUser user' <- privateGetUser userId' validateUserPassword_ user_ user' viewPwd_ @@ -557,27 +559,28 @@ processChatCommand' vr = \case StartChat mainApp -> withUser' $ \_ -> asks agentAsync >>= readTVarIO >>= \case Just _ -> pure CRChatRunning - _ -> checkStoreNotChanged $ startChatController mainApp $> CRChatStarted + _ -> checkStoreNotChanged . lift $ startChatController mainApp $> CRChatStarted APIStopChat -> do - ask >>= stopChatController + ask >>= liftIO . stopChatController pure CRChatStopped APIActivateChat restoreChat -> withUser $ \_ -> do - when restoreChat restoreCalls - withAgent foregroundAgent + lift $ when restoreChat restoreCalls + lift $ withAgent' foregroundAgent chatWriteVar chatActivated True when restoreChat $ do users <- withStore' getUsers - void . forkIO $ subscribeUsers True users - void . forkIO $ startFilesToReceive users - setAllExpireCIFlags True + lift $ do + void . forkIO $ subscribeUsers True users + void . forkIO $ startFilesToReceive users + setAllExpireCIFlags True ok_ APISuspendChat t -> do chatWriteVar chatActivated False - setAllExpireCIFlags False + lift $ setAllExpireCIFlags False stopRemoteCtrl - withAgent (`suspendAgent` t) + lift $ withAgent' (`suspendAgent` t) ok_ - ResubscribeAllConnections -> withStore' getUsers >>= subscribeUsers False >> ok_ + ResubscribeAllConnections -> withStore' getUsers >>= lift . subscribeUsers False >> ok_ -- has to be called before StartChat SetTempFolder tf -> do createDirectoryIfMissing True tf @@ -607,13 +610,13 @@ processChatCommand' vr = \case pure $ CRContactPQAllowed user ct' pqEnc Nothing -> throwChatError $ CEContactNotActive ct SetContactPQ cName pqEnc -> withContactName cName (`APISetContactPQ` pqEnc) - APIExportArchive cfg -> checkChatStopped $ exportArchive cfg >> ok_ + APIExportArchive cfg -> checkChatStopped $ lift (exportArchive cfg) >> ok_ ExportArchive -> do ts <- liftIO getCurrentTime let filePath = "simplex-chat." <> formatTime defaultTimeLocale "%FT%H%M%SZ" ts <> ".zip" processChatCommand $ APIExportArchive $ ArchiveConfig filePath Nothing Nothing APIImportArchive cfg -> checkChatStopped $ do - fileErrs <- importArchive cfg + fileErrs <- lift $ importArchive cfg setStoreChanged pure $ CRArchiveImported fileErrs APISaveAppSettings as -> withStore' (`saveAppSettings` as) >> ok_ @@ -683,11 +686,11 @@ processChatCommand' vr = \case startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) where - setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd)) + setupSndFileTransfer :: Contact -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) setupSndFileTransfer ct = forM file_ $ \file -> do fileSize <- checkSndFile file xftpSndFileTransfer user file fileSize 1 $ CGContact ct - prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> m (MsgContainer, Maybe (CIQuote 'CTDirect)) + prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> CM (MsgContainer, Maybe (CIQuote 'CTDirect)) prepareMsg fInv_ timed_ = case quotedItemId_ of Nothing -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) Just quotedItemId -> do @@ -699,7 +702,7 @@ processChatCommand' vr = \case quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) where - quoteData :: ChatItem c d -> m (MsgContent, CIQDirection 'CTDirect, Bool) + quoteData :: ChatItem c d -> CM (MsgContent, CIQDirection 'CTDirect, Bool) quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidQuote quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True) quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False) @@ -725,7 +728,7 @@ processChatCommand' vr = \case startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) notAllowedError f = pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText f)) - setupSndFileTransfer :: Group -> Int -> m (Maybe (FileInvitation, CIFile 'MDSnd)) + setupSndFileTransfer :: Group -> Int -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) setupSndFileTransfer g n = forM file_ $ \file -> do fileSize <- checkSndFile file xftpSndFileTransfer user file fileSize n $ CGGroup g @@ -733,7 +736,7 @@ processChatCommand' vr = \case CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd) + xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> CM (FileInvitation, CIFile 'MDSnd) xftpSndFileTransfer user file fileSize n contactOrGroup = do (fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup case contactOrGroup of @@ -756,7 +759,7 @@ processChatCommand' vr = \case let cd = CDLocalSnd nf ciId <- createLocalChatItem user cd content createdAt ciFile_ <- forM file_ $ \cf@CryptoFile {filePath, cryptoArgs} -> do - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cryptoArgs chunkSize <- asks $ fileChunkSize . config withStore' $ \db -> do @@ -994,7 +997,7 @@ processChatCommand' vr = \case withStore' $ \db -> deleteGroupItemsAndMembers db user gInfo members withStore' $ \db -> deleteGroup db user gInfo let contactIds = mapMaybe memberContactId members - (errs1, (errs2, connIds)) <- second unzip . partitionEithers <$> withStoreBatch (\db -> map (deleteUnusedContact db) contactIds) + (errs1, (errs2, connIds)) <- lift $ second unzip . partitionEithers <$> withStoreBatch (\db -> map (deleteUnusedContact db) contactIds) let errs = errs1 <> mapMaybe (fmap ChatErrorStore) errs2 unless (null errs) $ toView $ CRChatErrors (Just user) errs deleteAgentConnectionsAsync user $ concat connIds @@ -1132,7 +1135,7 @@ processChatCommand' vr = \case (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XCallEnd callId) updateCallItemStatus user ct call WCSDisconnected $ Just msgId pure Nothing - APIGetCallInvitations -> withUser $ \_ -> do + APIGetCallInvitations -> withUser $ \_ -> lift $ do calls <- asks currentCalls >>= readTVarIO let invs = mapMaybe callInvitation $ M.elems calls rcvCallInvitations <- rights <$> mapM rcvCallInvitation invs @@ -1193,12 +1196,12 @@ processChatCommand' vr = \case withChatLock "setUserSMPServers" $ do withStore $ \db -> overwriteProtocolServers db user servers cfg <- asks config - withAgent $ \a -> setProtocolServers a (aUserId user) $ activeAgentServers cfg p servers + lift $ withAgent' $ \a -> setProtocolServers a (aUserId user) $ activeAgentServers cfg p servers ok user SetUserProtoServers serversConfig -> withUser $ \User {userId} -> processChatCommand $ APISetUserProtoServers userId serversConfig APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user -> - CRServerTestResult user srv <$> withAgent (\a -> testProtocolServer a (aUserId user) server) + lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> @@ -1207,15 +1210,15 @@ processChatCommand' vr = \case case newTTL_ of Nothing -> do withStore' $ \db -> setChatItemTTL db user newTTL_ - setExpireCIFlag user False + lift $ setExpireCIFlag user False Just newTTL -> do oldTTL <- withStore' (`getChatItemTTL` user) when (maybe True (newTTL <) oldTTL) $ do - setExpireCIFlag user False + lift $ setExpireCIFlag user False expireChatItems user newTTL True withStore' $ \db -> setChatItemTTL db user newTTL_ - startExpireCIThread user - whenM chatStarted $ setExpireCIFlag user True + lift $ startExpireCIThread user + lift . whenM chatStarted $ setExpireCIFlag user True ok user SetChatItemTTL newTTL_ -> withUser' $ \User {userId} -> do processChatCommand $ APISetChatItemTTL userId newTTL_ @@ -1224,10 +1227,10 @@ processChatCommand' vr = \case pure $ CRChatItemTTL user ttl GetChatItemTTL -> withUser' $ \User {userId} -> do processChatCommand $ APIGetChatItemTTL userId - APISetNetworkConfig cfg -> withUser' $ \_ -> withAgent (`setNetworkConfig` cfg) >> ok_ + APISetNetworkConfig cfg -> withUser' $ \_ -> lift (withAgent' (`setNetworkConfig` cfg)) >> ok_ APIGetNetworkConfig -> withUser' $ \_ -> - CRNetworkConfig <$> withAgent getNetworkConfig - ReconnectAllServers -> withUser' $ \_ -> withAgent reconnectAllServers >> ok_ + lift $ CRNetworkConfig <$> withAgent' getNetworkConfig + ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_ APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of CTDirect -> do ct <- withStore $ \db -> do @@ -1429,7 +1432,7 @@ processChatCommand' vr = \case incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing let profileToSend = userProfileToSend user incognitoProfile Nothing False pqSup <- chatReadVar pqExperimentalEnabled - withAgent' (\a -> connRequestPQSupport a pqSup cReq) >>= \case + lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq -- TODO PQ the error above should be CEIncompatibleConnReqVersion, also the same API should be called in Plan Just (agentV, pqSup') -> do @@ -1563,19 +1566,40 @@ processChatCommand' vr = \case processChatCommand . APISendMessage chatRef True Nothing $ ComposedMessage Nothing Nothing mc SendMessageBroadcast msg -> withUser $ \user -> do contacts <- withStore' $ \db -> getUserContacts db vr user - let cts = filter (\ct -> contactReady ct && contactActive ct && directOrUsed ct) contacts - ChatConfig {logLevel} <- asks config withChatLock "sendMessageBroadcast" . procCmd $ do - (successes, failures) <- foldM (sendAndCount user logLevel) (0, 0) cts - timestamp <- liftIO getCurrentTime - pure CRBroadcastSent {user, msgContent = mc, successes, failures, timestamp} + let ctConns_ = L.nonEmpty $ foldr addContactConn [] contacts + case ctConns_ of + Nothing -> do + timestamp <- liftIO getCurrentTime + pure CRBroadcastSent {user, msgContent = mc, successes = 0, failures = 0, timestamp} + Just (ctConns :: NonEmpty (Contact, Connection)) -> do + let idsEvts = L.map ctSndEvent ctConns + sndMsgs <- lift $ createSndMessages idsEvts + let msgReqs_ :: NonEmpty (Either ChatError MsgReq) = L.zipWith (fmap . ctMsgReq) ctConns sndMsgs + (errs, ctSndMsgs :: [(Contact, SndMessage)]) <- + lift $ partitionEithers . L.toList . zipWith3' combineResults ctConns sndMsgs <$> deliverMessagesB msgReqs_ + timestamp <- liftIO getCurrentTime + lift . void $ withStoreBatch' $ \db -> map (createCI db user timestamp) ctSndMsgs + pure CRBroadcastSent {user, msgContent = mc, successes = length ctSndMsgs, failures = length errs, timestamp} where mc = MCText msg - sendAndCount user ll (s, f) ct = - (sendToContact user ct $> (s + 1, f)) `catchChatError` \e -> when (ll <= CLLInfo) (toView $ CRChatError (Just user) e) $> (s, f + 1) - sendToContact user ct = do - (sndMsg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) - void $ saveSndChatItem user (CDDirectSnd ct) sndMsg (CISndMsgContent mc) + addContactConn :: Contact -> [(Contact, Connection)] -> [(Contact, Connection)] + addContactConn ct ctConns = case contactSendConn_ ct of + Right conn | directOrUsed ct -> (ct, conn) : ctConns + _ -> ctConns + ctSndEvent :: (Contact, Connection) -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json) + ctSndEvent (_, Connection {connId, pqSupport}) = (ConnectionId connId, pqSupport, XMsgNew $ MCSimple (extMsgContent mc Nothing)) + ctMsgReq :: (Contact, Connection) -> SndMessage -> MsgReq + ctMsgReq (_, conn) SndMessage {msgId, msgBody} = (conn, MsgFlags {notification = hasNotification XMsgNew_}, msgBody, msgId) + zipWith3' :: (a -> b -> c -> d) -> NonEmpty a -> NonEmpty b -> NonEmpty c -> NonEmpty d + zipWith3' f ~(x :| xs) ~(y :| ys) ~(z :| zs) = f x y z :| zipWith3 f xs ys zs + combineResults :: (Contact, Connection) -> Either ChatError SndMessage -> Either ChatError (Int64, PQEncryption) -> Either ChatError (Contact, SndMessage) + combineResults (ct, _) (Right msg') (Right _) = Right (ct, msg') + combineResults _ (Left e) _ = Left e + combineResults _ _ (Left e) = Left e + createCI :: DB.Connection -> User -> UTCTime -> (Contact, SndMessage) -> IO () + createCI db user createdAt (ct, sndMsg) = + void $ createNewSndChatItem db user (CDDirectSnd ct) sndMsg (CISndMsgContent mc) Nothing Nothing False createdAt SendMessageQuote cName (AMsgDirection msgDir) quotedMsg msg -> withUser $ \user@User {userId} -> do contactId <- withStore $ \db -> getContactIdByName db user cName quotedItemId <- withStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg @@ -1821,7 +1845,7 @@ processChatCommand' vr = \case -- [incognito] reuse membership incognito profile ct <- withStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode -- TODO not sure it is correct to set connections status here? - setContactNetworkStatus ct NSConnected + lift $ setContactNetworkStatus ct NSConnected pure $ CRNewMemberContact user ct g m _ -> throwChatError CEGroupMemberNotActive APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do @@ -1898,7 +1922,7 @@ processChatCommand' vr = \case _ -> processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCFile "") SendImage chatName f@(CryptoFile fPath _) -> withUser $ \user -> do chatRef <- getChatRef user chatName - filePath <- toFSFilePath fPath + filePath <- lift $ toFSFilePath fPath unless (any (`isSuffixOf` map toLower fPath) imageExtensions) $ throwChatError CEFileImageType {filePath} fileSize <- getFileSize filePath unless (fileSize <= maxImageSize) $ throwChatError CEFileImageSize {filePath} @@ -1953,10 +1977,10 @@ processChatCommand' vr = \case pure $ CRRcvFileCancelled user ci ftr Just XFTPRcvFile {agentRcvFileId} -> do forM_ (liveRcvFileTransferPath ftr) $ \filePath -> do - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath liftIO $ removeFile fsFilePath `catchAll_` pure () - forM_ agentRcvFileId $ \(AgentRcvFileId aFileId) -> - withAgent (`xftpDeleteRcvFile` aFileId) + lift . forM_ agentRcvFileId $ \(AgentRcvFileId aFileId) -> + withAgent' (`xftpDeleteRcvFile` aFileId) ci <- withStore $ \db -> do liftIO $ do updateCIFileStatus db user fileId CIFSRcvInvitation @@ -2032,7 +2056,7 @@ processChatCommand' vr = \case ListRemoteCtrls -> withUser_ $ CRRemoteCtrlList <$> listRemoteCtrls DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ APIUploadStandaloneFile userId file@CryptoFile {filePath} -> withUserId userId $ \user -> do - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath fileSize <- liftIO $ CF.getFileContentsSize file {filePath = fsFilePath} when (fileSize > toInteger maxFileSizeHard) $ throwChatError $ CEFileSize filePath (_, _, fileTransferMeta) <- xftpSndFileTransfer_ user file fileSize 1 Nothing @@ -2048,18 +2072,18 @@ processChatCommand' vr = \case chatMigrations <- map upMigration <$> withStore' (Migrations.getCurrent . DB.conn) agentMigrations <- withAgent getAgentMigrations pure $ CRVersionInfo {versionInfo, chatMigrations, agentMigrations} - DebugLocks -> do + DebugLocks -> lift $ do chatLockName <- atomically . tryReadTMVar =<< asks chatLock - agentLocks <- withAgent debugAgentLocks + agentLocks <- withAgent' debugAgentLocks pure CRDebugLocks {chatLockName, agentLocks} - GetAgentWorkers -> CRAgentWorkersSummary <$> withAgent getAgentWorkersSummary - GetAgentWorkersDetails -> CRAgentWorkersDetails <$> withAgent getAgentWorkersDetails - GetAgentStats -> CRAgentStats . map stat <$> withAgent getAgentStats + GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary + GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails + GetAgentStats -> lift $ CRAgentStats . map stat <$> withAgent' getAgentStats where stat (AgentStatsKey {host, clientTs, cmd, res}, count) = map B.unpack [host, clientTs, cmd, res, bshow count] - ResetAgentStats -> withAgent resetAgentStats >> ok_ - GetAgentSubs -> summary <$> withAgent getAgentSubscriptions + ResetAgentStats -> lift (withAgent' resetAgentStats) >> ok_ + GetAgentSubs -> lift $ summary <$> withAgent' getAgentSubscriptions where summary SubscriptionsInfo {activeSubscriptions, pendingSubscriptions, removedSubscriptions} = CRAgentSubs @@ -2072,7 +2096,10 @@ processChatCommand' vr = \case accSubErrors m = \case SubInfo {server, subError = Just e} -> M.alter (Just . maybe [e] (e :)) server m _ -> m - GetAgentSubsDetails -> CRAgentSubsDetails <$> withAgent getAgentSubscriptions + GetAgentSubsDetails -> lift $ CRAgentSubsDetails <$> withAgent' getAgentSubscriptions + -- CustomChatCommand is unsupported, it can be processed in preCmdHook + -- in a modified CLI app or core - the hook should return Either ChatResponse ChatCommand + CustomChatCommand _cmd -> withUser $ \user -> pure $ chatCmdError (Just user) "not supported" where withChatLock name action = asks chatLock >>= \l -> withLock l name action -- below code would make command responses asynchronous where they can be slow @@ -2086,11 +2113,11 @@ processChatCommand' vr = \case -- (atomically . writeTBQueue q) . (Just corrId,) =<< (action `catchChatError` (pure . CRChatError)) -- pure $ CRCmdAccepted corrId -- use function below to make commands "synchronous" - procCmd :: m ChatResponse -> m ChatResponse + procCmd :: CM ChatResponse -> CM ChatResponse procCmd = id ok_ = pure $ CRCmdOk Nothing ok = pure . CRCmdOk . Just - getChatRef :: User -> ChatName -> m ChatRef + getChatRef :: User -> ChatName -> CM ChatRef getChatRef user (ChatName cType name) = ChatRef cType <$> case cType of CTDirect -> withStore $ \db -> getContactIdByName db user name @@ -2099,25 +2126,25 @@ processChatCommand' vr = \case | name == "" -> withStore (`getUserNoteFolderId` user) | otherwise -> throwChatError $ CECommandError "not supported" _ -> throwChatError $ CECommandError "not supported" - checkChatStopped :: m ChatResponse -> m ChatResponse + checkChatStopped :: CM ChatResponse -> CM ChatResponse checkChatStopped a = asks agentAsync >>= readTVarIO >>= maybe a (const $ throwChatError CEChatNotStopped) - setStoreChanged :: m () + setStoreChanged :: CM () setStoreChanged = asks chatStoreChanged >>= atomically . (`writeTVar` True) - withStoreChanged :: m () -> m ChatResponse + withStoreChanged :: CM () -> CM ChatResponse withStoreChanged a = checkChatStopped $ a >> setStoreChanged >> ok_ - checkStoreNotChanged :: m ChatResponse -> m ChatResponse + checkStoreNotChanged :: CM ChatResponse -> CM ChatResponse checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged) - withUserName :: UserName -> (UserId -> ChatCommand) -> m ChatResponse + withUserName :: UserName -> (UserId -> ChatCommand) -> CM ChatResponse withUserName uName cmd = withStore (`getUserIdByName` uName) >>= processChatCommand . cmd - withContactName :: ContactName -> (ContactId -> ChatCommand) -> m ChatResponse + withContactName :: ContactName -> (ContactId -> ChatCommand) -> CM ChatResponse withContactName cName cmd = withUser $ \user -> withStore (\db -> getContactIdByName db user cName) >>= processChatCommand . cmd - withMemberName :: GroupName -> ContactName -> (GroupId -> GroupMemberId -> ChatCommand) -> m ChatResponse + withMemberName :: GroupName -> ContactName -> (GroupId -> GroupMemberId -> ChatCommand) -> CM ChatResponse withMemberName gName mName cmd = withUser $ \user -> getGroupAndMemberId user gName mName >>= processChatCommand . uncurry cmd - getConnectionCode :: ConnId -> m Text + getConnectionCode :: ConnId -> CM Text getConnectionCode connId = verificationCode <$> withAgent (`getConnectionRatchetAdHash` connId) - verifyConnectionCode :: User -> Connection -> Maybe Text -> m ChatResponse + verifyConnectionCode :: User -> Connection -> Maybe Text -> CM ChatResponse verifyConnectionCode user conn@Connection {connId} (Just code) = do code' <- getConnectionCode $ aConnId conn let verified = sameVerificationCode code code' @@ -2127,19 +2154,19 @@ processChatCommand' vr = \case code' <- getConnectionCode $ aConnId conn withStore' $ \db -> setConnectionVerified db user connId Nothing pure $ CRConnectionVerified user False code' - getSentChatItemIdByText :: User -> ChatRef -> Text -> m Int64 + getSentChatItemIdByText :: User -> ChatRef -> Text -> CM Int64 getSentChatItemIdByText user@User {userId, localDisplayName} (ChatRef cType cId) msg = case cType of CTDirect -> withStore $ \db -> getDirectChatItemIdByText db userId cId SMDSnd msg CTGroup -> withStore $ \db -> getGroupChatItemIdByText db user cId (Just localDisplayName) msg CTLocal -> withStore $ \db -> getLocalChatItemIdByText db user cId SMDSnd msg _ -> throwChatError $ CECommandError "not supported" - getChatItemIdByText :: User -> ChatRef -> Text -> m Int64 + getChatItemIdByText :: User -> ChatRef -> Text -> CM Int64 getChatItemIdByText user (ChatRef cType cId) msg = case cType of CTDirect -> withStore $ \db -> getDirectChatItemIdByText' db user cId msg CTGroup -> withStore $ \db -> getGroupChatItemIdByText' db user cId msg CTLocal -> withStore $ \db -> getLocalChatItemIdByText' db user cId msg _ -> throwChatError $ CECommandError "not supported" - connectViaContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> m ChatResponse + connectViaContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> CM ChatResponse connectViaContact user@User {userId} incognito cReq@(CRContactUri ConnReqUriData {crClientData}) = withChatLock "connectViaContact" $ do let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq @@ -2169,7 +2196,7 @@ processChatCommand' vr = \case (connId, incognitoProfile, subMode, chatV) <- requestContact user incognito cReq xContactId inGroup pqSup conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup pure $ CRSentInvitation user conn incognitoProfile - connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> m ChatResponse + connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> CM ChatResponse connectContactViaAddress user incognito ct cReq = withChatLock "connectViaContact" $ do newXContactId <- XContactId <$> drgRandomBytes 16 @@ -2178,7 +2205,7 @@ processChatCommand' vr = \case let cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq ct' <- withStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup pure $ CRSentInvitationToContact user ct' incognitoProfile - requestContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> XContactId -> Bool -> PQSupport -> m (ConnId, Maybe Profile, SubscriptionMode, VersionChat) + requestContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> XContactId -> Bool -> PQSupport -> CM (ConnId, Maybe Profile, SubscriptionMode, VersionChat) requestContact user incognito cReq xContactId inGroup pqSup = do -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing @@ -2186,7 +2213,7 @@ processChatCommand' vr = \case -- 0) toggle disabled - PQSupportOff -- 1) toggle enabled, address supports PQ (connRequestPQSupport returns Just True) - PQSupportOn, enable support with compression -- 2) toggle enabled, address doesn't support PQ - PQSupportOn but without compression, with version range indicating support - withAgent' (\a -> connRequestPQSupport a pqSup cReq) >>= \case + lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq Just (agentV, _) -> do let chatV = agentToChatVersion agentV @@ -2198,16 +2225,16 @@ processChatCommand' vr = \case contactMember Contact {contactId} = find $ \GroupMember {memberContactId = cId, memberStatus = s} -> cId == Just contactId && s /= GSMemRemoved && s /= GSMemLeft - checkSndFile :: CryptoFile -> m Integer + checkSndFile :: CryptoFile -> CM Integer checkSndFile (CryptoFile f cfArgs) = do - fsFilePath <- toFSFilePath f + fsFilePath <- lift $ toFSFilePath f unlessM (doesFileExist fsFilePath) . throwChatError $ CEFileNotFound f fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cfArgs when (fromInteger fileSize > maxFileSize) $ throwChatError $ CEFileSize f pure fileSize - updateProfile :: User -> Profile -> m ChatResponse + updateProfile :: User -> Profile -> CM ChatResponse updateProfile user p' = updateProfile_ user p' $ withStore $ \db -> updateUserProfile db user p' - updateProfile_ :: User -> Profile -> m User -> m ChatResponse + updateProfile_ :: User -> Profile -> CM User -> CM ChatResponse updateProfile_ user@User {profile = p@LocalProfile {displayName = n}} p'@Profile {displayName = n'} updateUser | p' == fromLocalProfile p = pure $ CRUserProfileNoChange user | otherwise = do @@ -2221,12 +2248,12 @@ processChatCommand' vr = \case summary <- case changedCts_ of Nothing -> pure $ UserProfileUpdateSummary 0 0 [] Just changedCts -> do - let idsEvts = L.map ctSndMsg changedCts - msgReqs_ <- L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts - (errs, cts) <- partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ + let idsEvts = L.map ctSndEvent changedCts + msgReqs_ <- lift $ L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts + (errs, cts) <- lift $ partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ unless (null errs) $ toView $ CRChatErrors (Just user) errs let changedCts' = filter (\ChangedProfileContact {ct, ct'} -> directOrUsed ct' && mergedPreferences ct' /= mergedPreferences ct) cts - createContactsSndFeatureItems user' changedCts' + lift $ createContactsSndFeatureItems user' changedCts' pure UserProfileUpdateSummary { updateSuccesses = length cts, @@ -2238,21 +2265,20 @@ processChatCommand' vr = \case -- [incognito] filter out contacts with whom user has incognito connections addChangedProfileContact :: User -> Contact -> [ChangedProfileContact] -> [ChangedProfileContact] addChangedProfileContact user' ct changedCts = case contactSendConn_ ct' of - Left _ -> changedCts - Right conn - | connIncognito conn || mergedProfile' == mergedProfile -> changedCts - | otherwise -> ChangedProfileContact ct ct' mergedProfile' conn : changedCts + Right conn | not (connIncognito conn) && mergedProfile' /= mergedProfile -> + ChangedProfileContact ct ct' mergedProfile' conn : changedCts + _ -> changedCts where mergedProfile = userProfileToSend user Nothing (Just ct) False ct' = updateMergedPreferences user' ct mergedProfile' = userProfileToSend user' Nothing (Just ct') False - ctSndMsg :: ChangedProfileContact -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json) - ctSndMsg ChangedProfileContact {mergedProfile', conn = Connection {connId, pqSupport}} = (ConnectionId connId, pqSupport, XInfo mergedProfile') + ctSndEvent :: ChangedProfileContact -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json) + ctSndEvent ChangedProfileContact {mergedProfile', conn = Connection {connId, pqSupport}} = (ConnectionId connId, pqSupport, XInfo mergedProfile') ctMsgReq :: ChangedProfileContact -> Either ChatError SndMessage -> Either ChatError MsgReq ctMsgReq ChangedProfileContact {conn} = fmap $ \SndMessage {msgId, msgBody} -> (conn, MsgFlags {notification = hasNotification XInfo_}, msgBody, msgId) - updateContactPrefs :: User -> Contact -> Preferences -> m ChatResponse + updateContactPrefs :: User -> Contact -> Preferences -> CM ChatResponse updateContactPrefs _ ct@Contact {activeConn = Nothing} _ = throwChatError $ CEContactNotActive ct updateContactPrefs user@User {userId} ct@Contact {activeConn = Just Connection {customUserProfileId}, userPreferences = contactUserPrefs} contactUserPrefs' | contactUserPrefs == contactUserPrefs' = pure $ CRContactPrefsUpdated user ct ct @@ -2265,9 +2291,9 @@ processChatCommand' vr = \case when (mergedProfile' /= mergedProfile) $ withChatLock "updateProfile" $ do void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` (toView . CRChatError (Just user)) - when (directOrUsed ct') $ createSndFeatureItems user ct ct' + lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct' pure $ CRContactPrefsUpdated user ct ct' - runUpdateGroupProfile :: User -> Group -> GroupProfile -> m ChatResponse + runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do assertUserGroupRole g GROwner when (n /= n') $ checkValidName n' @@ -2279,30 +2305,30 @@ processChatCommand' vr = \case toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat g') ci) createGroupFeatureChangedItems user cd CISndGroupFeature g g' pure $ CRGroupUpdated user g g' Nothing - checkValidName :: GroupName -> m () + checkValidName :: GroupName -> CM () checkValidName displayName = do when (T.null displayName) $ throwChatError CEInvalidDisplayName {displayName, validName = ""} let validName = T.pack $ mkValidName $ T.unpack displayName when (displayName /= validName) $ throwChatError CEInvalidDisplayName {displayName, validName} - assertUserGroupRole :: GroupInfo -> GroupMemberRole -> m () + assertUserGroupRole :: GroupInfo -> GroupMemberRole -> CM () assertUserGroupRole g@GroupInfo {membership} requiredRole = do let GroupMember {memberRole = membershipMemRole} = membership when (membershipMemRole < requiredRole) $ throwChatError $ CEGroupUserRole g requiredRole when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined g) when (memberRemoved membership) $ throwChatError CEGroupMemberUserRemoved unless (memberActive membership) $ throwChatError CEGroupMemberNotActive - delGroupChatItem :: MsgDirectionI d => User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Maybe GroupMember -> m ChatResponse + delGroupChatItem :: MsgDirectionI d => User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Maybe GroupMember -> CM ChatResponse delGroupChatItem user gInfo ci msgId byGroupMember = do deletedTs <- liftIO getCurrentTime if groupFeatureAllowed SGFFullDelete gInfo then deleteGroupCI user gInfo ci True False byGroupMember deletedTs else markGroupCIDeleted user gInfo ci msgId True byGroupMember deletedTs - updateGroupProfileByName :: GroupName -> (GroupProfile -> GroupProfile) -> m ChatResponse + updateGroupProfileByName :: GroupName -> (GroupProfile -> GroupProfile) -> CM ChatResponse updateGroupProfileByName gName update = withUser $ \user -> do g@(Group GroupInfo {groupProfile = p} _) <- withStore $ \db -> getGroupIdByName db user gName >>= getGroup db vr user runUpdateGroupProfile user g $ update p - withCurrentCall :: ContactId -> (User -> Contact -> Call -> m (Maybe Call)) -> m ChatResponse + withCurrentCall :: ContactId -> (User -> Contact -> Call -> CM (Maybe Call)) -> CM ChatResponse withCurrentCall ctId action = do (user, ct) <- withStore $ \db -> do user <- getUserByContactId db ctId @@ -2323,11 +2349,11 @@ processChatCommand' vr = \case atomically $ TM.delete ctId calls ok user | otherwise -> throwChatError $ CECallContact contactId - withServerProtocol :: ProtocolTypeI p => SProtocolType p -> (UserProtocol p => m a) -> m a + withServerProtocol :: ProtocolTypeI p => SProtocolType p -> (UserProtocol p => CM a) -> CM a withServerProtocol p action = case userProtocol p of Just Dict -> action _ -> throwChatError $ CEServerProtocol $ AProtocolType p - forwardFile :: ChatName -> FileTransferId -> (ChatName -> CryptoFile -> ChatCommand) -> m ChatResponse + forwardFile :: ChatName -> FileTransferId -> (ChatName -> CryptoFile -> ChatCommand) -> CM ChatResponse forwardFile chatName fileId sendCommand = withUser $ \user -> do withStore (\db -> getFileTransfer db user fileId) >>= \case FTRcv RcvFileTransfer {fileStatus = RFSComplete RcvFileInfo {filePath}, cryptoArgs} -> forward filePath cryptoArgs @@ -2335,13 +2361,13 @@ processChatCommand' vr = \case _ -> throwChatError CEFileNotReceived {fileId} where forward path cfArgs = processChatCommand . sendCommand chatName $ CryptoFile path cfArgs - getGroupAndMemberId :: User -> GroupName -> ContactName -> m (GroupId, GroupMemberId) + getGroupAndMemberId :: User -> GroupName -> ContactName -> CM (GroupId, GroupMemberId) getGroupAndMemberId user gName groupMemberName = withStore $ \db -> do groupId <- getGroupIdByName db user gName groupMemberId <- getGroupMemberIdByName db user groupId groupMemberName pure (groupId, groupMemberId) - sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> m () + sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM () sendGrpInvitation user ct@Contact {localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupMember {memberRole = userRole, memberId = userMemberId} = membership @@ -2358,27 +2384,27 @@ processChatCommand' vr = \case let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole ci <- saveSndChatItem user (CDDirectSnd ct) msg content toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) - sndContactCITimed :: Bool -> Contact -> Maybe Int -> m (Maybe CITimed) + sndContactCITimed :: Bool -> Contact -> Maybe Int -> CM (Maybe CITimed) sndContactCITimed live = sndCITimed_ live . contactTimedTTL - sndGroupCITimed :: Bool -> GroupInfo -> Maybe Int -> m (Maybe CITimed) + sndGroupCITimed :: Bool -> GroupInfo -> Maybe Int -> CM (Maybe CITimed) sndGroupCITimed live = sndCITimed_ live . groupTimedTTL - sndCITimed_ :: Bool -> Maybe (Maybe Int) -> Maybe Int -> m (Maybe CITimed) + sndCITimed_ :: Bool -> Maybe (Maybe Int) -> Maybe Int -> CM (Maybe CITimed) sndCITimed_ live chatTTL itemTTL = forM (chatTTL >>= (itemTTL <|>)) $ \ttl -> CITimed ttl <$> if live then pure Nothing else Just . addUTCTime (realToFrac ttl) <$> liftIO getCurrentTime - drgRandomBytes :: Int -> m ByteString + drgRandomBytes :: Int -> CM ByteString drgRandomBytes n = asks random >>= atomically . C.randomBytes n - privateGetUser :: UserId -> m User + privateGetUser :: UserId -> CM User privateGetUser userId = tryChatError (withStore (`getUser` userId)) >>= \case Left _ -> throwChatError CEUserUnknown Right user -> pure user - validateUserPassword :: User -> User -> Maybe UserPwd -> m () + validateUserPassword :: User -> User -> Maybe UserPwd -> CM () validateUserPassword = validateUserPassword_ . Just - validateUserPassword_ :: Maybe User -> User -> Maybe UserPwd -> m () + validateUserPassword_ :: Maybe User -> User -> Maybe UserPwd -> CM () validateUserPassword_ user_ User {userId = userId', viewPwdHash} viewPwd_ = forM_ viewPwdHash $ \pwdHash -> let userId_ = (\User {userId} -> userId) <$> user_ @@ -2389,13 +2415,13 @@ processChatCommand' vr = \case validPassword :: Text -> UserPwdHash -> Bool validPassword pwd UserPwdHash {hash = B64UrlByteString hash, salt = B64UrlByteString salt} = hash == C.sha512Hash (encodeUtf8 pwd <> salt) - setUserNotifications :: UserId -> Bool -> m ChatResponse + setUserNotifications :: UserId -> Bool -> CM ChatResponse setUserNotifications userId' showNtfs = withUser $ \user -> do user' <- privateGetUser userId' case viewPwdHash user' of Just _ -> throwChatError $ CEHiddenUserAlwaysMuted userId' _ -> setUserPrivacy user user' {showNtfs} - setUserPrivacy :: User -> User -> m ChatResponse + setUserPrivacy :: User -> User -> CM ChatResponse setUserPrivacy user@User {userId} user'@User {userId = userId'} | userId == userId' = do asks currentUser >>= atomically . (`writeTVar` Just user') @@ -2404,12 +2430,12 @@ processChatCommand' vr = \case | otherwise = do withStore' (`updateUserPrivacy` user') pure $ CRUserPrivacy {user, updatedUser = user'} - checkDeleteChatUser :: User -> m () + checkDeleteChatUser :: User -> CM () checkDeleteChatUser user@User {userId} = do users <- withStore' getUsers let otherVisible = filter (\User {userId = userId', viewPwdHash} -> userId /= userId' && isNothing viewPwdHash) users when (activeUser user && length otherVisible > 0) $ throwChatError (CECantDeleteActiveUser userId) - deleteChatUser :: User -> Bool -> m ChatResponse + deleteChatUser :: User -> Bool -> CM ChatResponse deleteChatUser user delSMPQueues = do filesInfo <- withStore' (`getUserFileInfo` user) cancelFilesInProgress user filesInfo @@ -2418,7 +2444,7 @@ processChatCommand' vr = \case withStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing ok_ - updateChatSettings :: ChatName -> (ChatSettings -> ChatSettings) -> m ChatResponse + updateChatSettings :: ChatName -> (ChatSettings -> ChatSettings) -> CM ChatResponse updateChatSettings (ChatName cType name) updateSettings = withUser $ \user -> do (chatId, chatSettings) <- case cType of CTDirect -> withStore $ \db -> do @@ -2432,7 +2458,7 @@ processChatCommand' vr = \case pure (gId, chatSettings) _ -> throwChatError $ CECommandError "not supported" processChatCommand $ APISetChatSettings (ChatRef cType chatId) $ updateSettings chatSettings - connectPlan :: User -> AConnectionRequestUri -> m ConnectionPlan + connectPlan :: User -> AConnectionRequestUri -> CM ConnectionPlan connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do withStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case Nothing -> pure $ CPInvitationLink ILPOk @@ -2510,7 +2536,7 @@ processChatCommand' vr = \case updateDirectChatItemView user ct itemId aciContent False Nothing _ -> pure () -- prohibited -toggleNtf :: ChatMonad m => User -> GroupMember -> Bool -> m () +toggleNtf :: User -> GroupMember -> Bool -> CM () toggleNtf user m ntfOn = when (memberActive m) $ forM_ (memberConnId m) $ \connId -> @@ -2523,7 +2549,7 @@ data ChangedProfileContact = ChangedProfileContact conn :: Connection } -prepareGroupMsg :: forall m. ChatMonad m => User -> GroupInfo -> MsgContent -> Maybe ChatItemId -> Maybe FileInvitation -> Maybe CITimed -> Bool -> m (MsgContainer, Maybe (CIQuote 'CTGroup)) +prepareGroupMsg :: User -> GroupInfo -> MsgContent -> Maybe ChatItemId -> Maybe FileInvitation -> Maybe CITimed -> Bool -> CM (MsgContainer, Maybe (CIQuote 'CTGroup)) prepareGroupMsg user GroupInfo {groupId, membership} mc quotedItemId_ fInv_ timed_ live = case quotedItemId_ of Nothing -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) Just quotedItemId -> do @@ -2535,7 +2561,7 @@ prepareGroupMsg user GroupInfo {groupId, membership} mc quotedItemId_ fInv_ time quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) where - quoteData :: ChatItem c d -> GroupMember -> m (MsgContent, CIQDirection 'CTGroup, Bool, GroupMember) + quoteData :: ChatItem c d -> GroupMember -> CM (MsgContent, CIQDirection 'CTGroup, Bool, GroupMember) quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} _ = throwChatError CEInvalidQuote quoteData ChatItem {chatDir = CIGroupSnd, content = CISndMsgContent qmc} membership' = pure (qmc, CIQGroupSnd, True, membership') quoteData ChatItem {chatDir = CIGroupRcv m, content = CIRcvMsgContent qmc} _ = pure (qmc, CIQGroupRcv $ Just m, False, m) @@ -2567,7 +2593,7 @@ quoteContent mc qmc ciFile_ qFileName = maybe qText (T.pack . getFileName) ciFile_ qTextOrFile = if T.null qText then qFileName else qText -assertDirectAllowed :: ChatMonad m => User -> MsgDirection -> Contact -> CMEventTag e -> m () +assertDirectAllowed :: User -> MsgDirection -> Contact -> CMEventTag e -> CM () assertDirectAllowed user dir ct event = unless (allowedChatEvent || anyDirectOrUsed ct) . unlessM directMessagesAllowed $ throwChatError (CEDirectMessagesProhibited dir ct) @@ -2587,12 +2613,12 @@ roundedFDCount n | n <= 0 = 4 | otherwise = max 4 $ fromIntegral $ (2 :: Integer) ^ (ceiling (logBase 2 (fromIntegral n) :: Double) :: Integer) -startExpireCIThread :: forall m. ChatMonad' m => User -> m () +startExpireCIThread :: User -> CM' () startExpireCIThread user@User {userId} = do expireThreads <- asks expireCIThreads atomically (TM.lookup userId expireThreads) >>= \case Nothing -> do - a <- Just <$> async (void $ runExceptT runExpireCIs) + a <- Just <$> async runExpireCIs atomically $ TM.insert userId a expireThreads _ -> pure () where @@ -2601,37 +2627,37 @@ startExpireCIThread user@User {userId} = do liftIO $ threadDelay' delay interval <- asks $ ciExpirationInterval . config forever $ do - flip catchChatError (toView . CRChatError (Just user)) $ do + flip catchChatError' (toView' . CRChatError (Just user)) $ do expireFlags <- asks expireCIFlags atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry - waitChatStartedAndActivated + lift waitChatStartedAndActivated ttl <- withStore' (`getChatItemTTL` user) forM_ ttl $ \t -> expireChatItems user t False liftIO $ threadDelay' interval -setExpireCIFlag :: ChatMonad' m => User -> Bool -> m () +setExpireCIFlag :: User -> Bool -> CM' () setExpireCIFlag User {userId} b = do expireFlags <- asks expireCIFlags atomically $ TM.insert userId b expireFlags -setAllExpireCIFlags :: ChatMonad' m => Bool -> m () +setAllExpireCIFlags :: Bool -> CM' () setAllExpireCIFlags b = do expireFlags <- asks expireCIFlags atomically $ do keys <- M.keys <$> readTVar expireFlags forM_ keys $ \k -> TM.insert k b expireFlags -cancelFilesInProgress :: forall m. ChatMonad m => User -> [CIFileInfo] -> m () +cancelFilesInProgress :: User -> [CIFileInfo] -> CM () cancelFilesInProgress user filesInfo = do let filesInfo' = filter (not . fileEnded) filesInfo - (sfs, rfs) <- splitFTTypes <$> withStoreBatch (\db -> map (getFT db) filesInfo') - forM_ rfs $ \RcvFileTransfer {fileId} -> closeFileHandle fileId rcvFiles `catchChatError` \_ -> pure () - void . withStoreBatch' $ \db -> map (updateSndFileCancelled db) sfs - void . withStoreBatch' $ \db -> map (updateRcvFileCancelled db) rfs + (sfs, rfs) <- lift $ splitFTTypes <$> withStoreBatch (\db -> map (getFT db) filesInfo') + forM_ rfs $ \RcvFileTransfer {fileId} -> lift (closeFileHandle fileId rcvFiles) `catchChatError` \_ -> pure () + lift . void . withStoreBatch' $ \db -> map (updateSndFileCancelled db) sfs + lift . void . withStoreBatch' $ \db -> map (updateRcvFileCancelled db) rfs let xsfIds = mapMaybe (\(FileTransferMeta {fileId, xftpSndFile}, _) -> (,fileId) <$> xftpSndFile) sfs xrfIds = mapMaybe (\RcvFileTransfer {fileId, xftpRcvFile} -> (,fileId) <$> xftpRcvFile) rfs - agentXFTPDeleteSndFilesRemote user xsfIds - agentXFTPDeleteRcvFiles xrfIds + lift $ agentXFTPDeleteSndFilesRemote user xsfIds + lift $ agentXFTPDeleteRcvFiles xrfIds let smpSFConnIds = concatMap (\(ft, sfts) -> mapMaybe (smpSndFileConnId ft) sfts) sfs smpRFConnIds = mapMaybe smpRcvFileConnId rfs deleteAgentConnectionsAsync user smpSFConnIds @@ -2673,7 +2699,7 @@ cancelFilesInProgress user filesInfo = do | otherwise = Nothing sndFTEnded SndFileTransfer {fileStatus} = fileStatus == FSCancelled || fileStatus == FSComplete -deleteFilesLocally :: forall m. ChatMonad m => [CIFileInfo] -> m () +deleteFilesLocally :: [CIFileInfo] -> CM () deleteFilesLocally files = withFilesFolder $ \filesFolder -> liftIO . forM_ files $ \CIFileInfo {filePath} -> @@ -2684,20 +2710,20 @@ deleteFilesLocally files = removeFile fPath `catchAll` \_ -> removePathForcibly fPath `catchAll_` pure () -- perform an action only if filesFolder is set (i.e. on mobile devices) - withFilesFolder :: (FilePath -> m ()) -> m () + withFilesFolder :: (FilePath -> CM ()) -> CM () withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action -updateCallItemStatus :: ChatMonad m => User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> m () +updateCallItemStatus :: User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> CM () updateCallItemStatus user ct Call {chatItemId} receivedStatus msgId_ = do aciContent_ <- callStatusItemContent user ct chatItemId receivedStatus forM_ aciContent_ $ \aciContent -> updateDirectChatItemView user ct chatItemId aciContent False msgId_ -updateDirectChatItemView :: ChatMonad m => User -> Contact -> ChatItemId -> ACIContent -> Bool -> Maybe MessageId -> m () +updateDirectChatItemView :: User -> Contact -> ChatItemId -> ACIContent -> Bool -> Maybe MessageId -> CM () updateDirectChatItemView user ct chatItemId (ACIContent msgDir ciContent) live msgId_ = do ci' <- withStore $ \db -> updateDirectChatItem db user ct chatItemId ciContent live msgId_ toView $ CRChatItemUpdated user (AChatItem SCTDirect msgDir (DirectChat ct) ci') -callStatusItemContent :: ChatMonad m => User -> Contact -> ChatItemId -> WebRTCCallStatus -> m (Maybe ACIContent) +callStatusItemContent :: User -> Contact -> ChatItemId -> WebRTCCallStatus -> CM (Maybe ACIContent) callStatusItemContent user Contact {contactId} chatItemId receivedStatus = do CChatItem msgDir ChatItem {meta = CIMeta {updatedAt}, content} <- withStore $ \db -> getDirectChatItem db user contactId chatItemId @@ -2729,17 +2755,17 @@ callStatusItemContent user Contact {contactId} chatItemId receivedStatus = do -- mobile clients use file paths relative to app directory (e.g. for the reason ios app directory changes on updates), -- so we have to differentiate between the file path stored in db and communicated with frontend, and the file path -- used during file transfer for actual operations with file system -toFSFilePath :: ChatMonad' m => FilePath -> m FilePath +toFSFilePath :: FilePath -> CM' FilePath toFSFilePath f = maybe f ( f) <$> (readTVarIO =<< asks filesFolder) -setFileToEncrypt :: ChatMonad m => RcvFileTransfer -> m RcvFileTransfer +setFileToEncrypt :: RcvFileTransfer -> CM RcvFileTransfer setFileToEncrypt ft@RcvFileTransfer {fileId} = do cfArgs <- atomically . CF.randomArgs =<< asks random withStore' $ \db -> setFileCryptoArgs db fileId cfArgs pure (ft :: RcvFileTransfer) {cryptoArgs = Just cfArgs} -receiveFile' :: ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m ChatResponse +receiveFile' :: User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> CM ChatResponse receiveFile' user ft rcvInline_ filePath_ = do (CRRcvFileAccepted user <$> acceptFileReceive user ft rcvInline_ filePath_) `catchChatError` processError where @@ -2749,7 +2775,7 @@ receiveFile' user ft rcvInline_ filePath_ = do ChatErrorAgent (CONN DUPLICATE) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft e -> throwError e -acceptFileReceive :: forall m. ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m AChatItem +acceptFileReceive :: User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> CM AChatItem acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = FileInvitation {fileName = fName, fileConnReq, fileInline, fileSize}, fileStatus, grpMemberId, cryptoArgs} rcvInline_ filePath_ = do unless (fileStatus == RFSNew) $ case fileStatus of RFSCancelled _ -> throwChatError $ CEFileCancelled fName @@ -2789,7 +2815,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI _ -> throwChatError $ CEFileInternal "member connection not active" _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" where - acceptFile :: CommandFunction -> (ChatMsgEvent 'Json -> m ()) -> m AChatItem + acceptFile :: CommandFunction -> (ChatMsgEvent 'Json -> CM ()) -> CM AChatItem acceptFile cmdFunction send = do filePath <- getRcvFilePath fileId filePath_ fName True inline <- receiveInline @@ -2807,7 +2833,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI subMode <- chatReadVar subscriptionMode connIds <- createAgentConnectionAsync user cmdFunction True SCMInvitation subMode withStore $ \db -> acceptRcvFileTransfer db vr user fileId connIds ConnNew filePath subMode - receiveInline :: m Bool + receiveInline :: CM Bool receiveInline = do ChatConfig {fileChunkSize, inlineFiles = InlineFilesConfig {receiveChunks, offerChunks}} <- asks config pure $ @@ -2817,7 +2843,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI || (rcvInline_ == Just True && fileSize <= fileChunkSize * offerChunks) ) -receiveViaCompleteFD :: ChatMonad m => User -> FileTransferId -> RcvFileDescr -> Maybe CryptoFileArgs -> m () +receiveViaCompleteFD :: User -> FileTransferId -> RcvFileDescr -> Maybe CryptoFileArgs -> CM () receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} cfArgs = when fileDescrComplete $ do rd <- parseFileDescription fileDescrText @@ -2825,7 +2851,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} startReceivingFile user fileId withStore' $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) -receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m RcvFileTransfer +receiveViaURI :: User -> FileDescriptionURI -> CryptoFile -> CM RcvFileTransfer receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do fileId <- withStore $ \db -> createRcvStandaloneFileTransfer db userId cf fileSize chunkSize aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) description cryptoArgs @@ -2838,7 +2864,7 @@ receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile where FD.ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description -startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () +startReceivingFile :: User -> FileTransferId -> CM () startReceivingFile user fileId = do vr <- chatVersionRange ci <- withStore $ \db -> do @@ -2847,16 +2873,16 @@ startReceivingFile user fileId = do getChatItemByFileId db vr user fileId toView $ CRRcvFileStart user ci -getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> String -> Bool -> m FilePath +getRcvFilePath :: FileTransferId -> Maybe FilePath -> String -> Bool -> CM FilePath getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of Nothing -> chatReadVar filesFolder >>= \case Nothing -> do - defaultFolder <- getDefaultFilesFolder - fPath <- defaultFolder `uniqueCombine` fn + defaultFolder <- lift getDefaultFilesFolder + fPath <- liftIO $ defaultFolder `uniqueCombine` fn createEmptyFile fPath $> fPath Just filesFolder -> do - fPath <- filesFolder `uniqueCombine` fn + fPath <- liftIO $ filesFolder `uniqueCombine` fn createEmptyFile fPath pure $ takeFileName fPath Just fPath -> @@ -2868,21 +2894,21 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of (throwChatError $ CEFileAlreadyExists fPath) (createEmptyFile fPath $> fPath) where - createInPassedDirectory :: FilePath -> m FilePath + createInPassedDirectory :: FilePath -> CM FilePath createInPassedDirectory fPathDir = do - fPath <- fPathDir `uniqueCombine` fn + fPath <- liftIO $ fPathDir `uniqueCombine` fn createEmptyFile fPath $> fPath - createEmptyFile :: FilePath -> m () + createEmptyFile :: FilePath -> CM () createEmptyFile fPath = emptyFile `catchThrow` (ChatError . CEFileWrite fPath . show) where - emptyFile :: m () + emptyFile :: CM () emptyFile | keepHandle = do h <- getFileHandle fileId fPath rcvFiles AppendMode liftIO $ B.hPut h "" >> hFlush h | otherwise = liftIO $ B.writeFile fPath "" -acceptContactRequest :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> m Contact +acceptContactRequest :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> CM Contact acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognitoProfile contactUsed = do subMode <- chatReadVar subscriptionMode pqSup <- chatReadVar pqExperimentalEnabled @@ -2894,7 +2920,7 @@ acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId inv acId <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode withStore' $ \db -> createAcceptedContact db user acId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed -acceptContactRequestAsync :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> m Contact +acceptContactRequestAsync :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> CM Contact acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do subMode <- chatReadVar subscriptionMode let profileToSend = profileToSendOnAccept user incognitoProfile False @@ -2906,7 +2932,7 @@ acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvI forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId pure ct -acceptGroupJoinRequestAsync :: ChatMonad m => User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> m GroupMember +acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember acceptGroupJoinRequestAsync user gInfo@GroupInfo {groupProfile, membership} @@ -2942,30 +2968,30 @@ profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> NewIncognito p -> p ExistingIncognito lp -> fromLocalProfile lp -deleteGroupLink' :: ChatMonad m => User -> GroupInfo -> m () +deleteGroupLink' :: User -> GroupInfo -> CM () deleteGroupLink' user gInfo = do vr <- chatVersionRange conn <- withStore $ \db -> getGroupLinkConnection db vr user gInfo deleteGroupLink_ user gInfo conn -deleteGroupLinkIfExists :: ChatMonad m => User -> GroupInfo -> m () +deleteGroupLinkIfExists :: User -> GroupInfo -> CM () deleteGroupLinkIfExists user gInfo = do vr <- chatVersionRange conn_ <- eitherToMaybe <$> withStore' (\db -> runExceptT $ getGroupLinkConnection db vr user gInfo) mapM_ (deleteGroupLink_ user gInfo) conn_ -deleteGroupLink_ :: ChatMonad m => User -> GroupInfo -> Connection -> m () +deleteGroupLink_ :: User -> GroupInfo -> Connection -> CM () deleteGroupLink_ user gInfo conn = do deleteAgentConnectionAsync user $ aConnId conn withStore' $ \db -> deleteGroupLink db user gInfo -agentSubscriber :: forall m. (MonadUnliftIO m, MonadReader ChatController m) => m () +agentSubscriber :: CM' () agentSubscriber = do q <- asks $ subQ . smpAgent l <- asks chatLock - forever $ atomically (readTBQueue q) >>= void . process l + forever $ atomically (readTBQueue q) >>= process l where - process :: Lock -> (ACorrId, EntityId, APartyCmd 'Agent) -> m (Either ChatError ()) + process :: Lock -> (ACorrId, EntityId, APartyCmd 'Agent) -> CM' () process l (corrId, entId, APC e msg) = run $ case e of SAENone -> processAgentMessageNoConn msg SAEConn -> processAgentMessage corrId entId msg @@ -2974,13 +3000,13 @@ agentSubscriber = do where run action = do let name = "agentSubscriber entity=" <> show e <> " entId=" <> str entId <> " msg=" <> str (aCommandTag msg) - withLock l name $ runExceptT $ action `catchChatError` (toView . CRChatError Nothing) + withLock' l name $ action `catchChatError'` (toView' . CRChatError Nothing) str :: StrEncoding a => a -> String str = B.unpack . strEncode -type AgentBatchSubscribe m = AgentClient -> [ConnId] -> ExceptT AgentErrorType m (Map ConnId (Either AgentErrorType ())) +type AgentBatchSubscribe = AgentClient -> [ConnId] -> ExceptT AgentErrorType IO (Map ConnId (Either AgentErrorType ())) -subscribeUserConnections :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> Bool -> AgentBatchSubscribe m -> User -> m () +subscribeUserConnections :: (PQSupport -> VersionRangeChat) -> Bool -> AgentBatchSubscribe -> User -> CM () subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do -- get user connections ce <- asks $ subscriptionEvents . config @@ -3034,37 +3060,37 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do createdAt, updatedAt = createdAt } - getContactConns :: m ([ConnId], Map ConnId Contact) + getContactConns :: CM ([ConnId], Map ConnId Contact) getContactConns = do cts <- withStore_ (`getUserContacts` vr) let cts' = mapMaybe (\ct -> (,ct) <$> contactConnId ct) $ filter contactActive cts pure (map fst cts', M.fromList cts') - getUserContactLinkConns :: m ([ConnId], Map ConnId UserContact) + getUserContactLinkConns :: CM ([ConnId], Map ConnId UserContact) getUserContactLinkConns = do (cs, ucs) <- unzip <$> withStore_ (`getUserContactLinks` vr) let connIds = map aConnId cs pure (connIds, M.fromList $ zip connIds ucs) - getGroupMemberConns :: m ([Group], [ConnId], Map ConnId GroupMember) + getGroupMemberConns :: CM ([Group], [ConnId], Map ConnId GroupMember) getGroupMemberConns = do gs <- withStore_ (`getUserGroups` vr) let mPairs = concatMap (\(Group _ ms) -> mapMaybe (\m -> (,m) <$> memberConnId m) (filter (not . memberRemoved) ms)) gs pure (gs, map fst mPairs, M.fromList mPairs) - getSndFileTransferConns :: m ([ConnId], Map ConnId SndFileTransfer) + getSndFileTransferConns :: CM ([ConnId], Map ConnId SndFileTransfer) getSndFileTransferConns = do sfts <- withStore_ getLiveSndFileTransfers let connIds = map sndFileTransferConnId sfts pure (connIds, M.fromList $ zip connIds sfts) - getRcvFileTransferConns :: m ([ConnId], Map ConnId RcvFileTransfer) + getRcvFileTransferConns :: CM ([ConnId], Map ConnId RcvFileTransfer) getRcvFileTransferConns = do rfts <- withStore_ getLiveRcvFileTransfers let rftPairs = mapMaybe (\ft -> (,ft) <$> liveRcvFileTransferConnId ft) rfts pure (map fst rftPairs, M.fromList rftPairs) - getPendingContactConns :: m ([ConnId], Map ConnId PendingContactConnection) + getPendingContactConns :: CM ([ConnId], Map ConnId PendingContactConnection) getPendingContactConns = do pcs <- withStore_ getPendingContactConnections let connIds = map aConnId' pcs pure (connIds, M.fromList $ zip connIds pcs) - contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> m () + contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> CM () contactSubsToView rs cts ce = do chatModifyVar connNetworkStatuses $ M.union (M.fromList statuses) ifM (asks $ coreApi . config) (notifyAPI statuses) notifyCLI @@ -3090,16 +3116,16 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do ChatErrorAgent (SMP SMP.AUTH) _ -> "contact deleted" e -> show e -- TODO possibly below could be replaced with less noisy events for API - contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> m () + contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> CM () contactLinkSubsToView rs = toView . CRUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs - groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [Group] -> Map ConnId GroupMember -> Bool -> m () + groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [Group] -> Map ConnId GroupMember -> Bool -> CM () groupSubsToView rs gs ms ce = do mapM_ groupSub $ sortOn (\(Group GroupInfo {localDisplayName = g} _) -> g) gs toView . CRMemberSubSummary user $ map (uncurry MemberSubStatus) mRs where mRs = resultsFor rs ms - groupSub :: Group -> m () + groupSub :: Group -> CM () groupSub (Group g@GroupInfo {membership, groupId = gId} members) = do when ce $ mapM_ (toView . uncurry (CRMemberSubError user g)) mErrors toView groupEvent @@ -3117,7 +3143,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do then CRGroupEmpty user g else CRGroupRemoved user g | otherwise = CRGroupSubscribed user g - sndFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId SndFileTransfer -> m () + sndFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId SndFileTransfer -> CM () sndFileSubsToView rs sfts = do let sftRs = resultsFor rs sfts forM_ sftRs $ \(ft@SndFileTransfer {fileId, fileStatus}, err_) -> do @@ -3127,11 +3153,11 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do l <- asks chatLock when (fileStatus == FSConnected) . unlessM (isFileActive fileId sndFiles) . withLock l "subscribe sendFileChunk" $ sendFileChunk user ft - rcvFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId RcvFileTransfer -> m () + rcvFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId RcvFileTransfer -> CM () rcvFileSubsToView rs = mapM_ (toView . uncurry (CRRcvFileSubError user)) . filterErrors . resultsFor rs - pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> m () + pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> CM () pendingConnSubsToView rs = toView . CRPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs - withStore_ :: (DB.Connection -> User -> IO [a]) -> m [a] + withStore_ :: (DB.Connection -> User -> IO [a]) -> CM [a] withStore_ a = withStore' (`a` user) `catchChatError` \e -> toView (CRChatError (Just user) e) $> [] filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)] filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_) @@ -3146,7 +3172,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do Just _ -> Nothing _ -> Just . ChatError . CEAgentNoSubResult $ AgentConnId connId -cleanupManager :: forall m. ChatMonad m => m () +cleanupManager :: CM () cleanupManager = do interval <- asks (cleanupManagerInterval . config) runWithoutInitialDelay interval @@ -3155,17 +3181,18 @@ cleanupManager = do stepDelay <- asks (cleanupManagerStepDelay . config) forever $ do flip catchChatError (toView . CRChatError Nothing) $ do - waitChatStartedAndActivated + lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ cleanupUser interval stepDelay forM_ us' $ cleanupUser interval stepDelay cleanupMessages `catchChatError` (toView . CRChatError Nothing) + -- TODO possibly, also cleanup async commands cleanupProbes `catchChatError` (toView . CRChatError Nothing) liftIO $ threadDelay' $ diffToMicroseconds interval where runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CRChatError Nothing) $ do - waitChatStartedAndActivated + lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u)) @@ -3195,14 +3222,14 @@ cleanupManager = do let cutoffTs = addUTCTime (-(14 * nominalDay)) ts withStore' (`deleteOldProbes` cutoffTs) -startProximateTimedItemThread :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m () +startProximateTimedItemThread :: User -> (ChatRef, ChatItemId) -> UTCTime -> CM () startProximateTimedItemThread user itemRef deleteAt = do interval <- asks (cleanupManagerInterval . config) ts <- liftIO getCurrentTime when (diffUTCTime deleteAt ts <= interval) $ startTimedItemThread user itemRef deleteAt -startTimedItemThread :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m () +startTimedItemThread :: User -> (ChatRef, ChatItemId) -> UTCTime -> CM () startTimedItemThread user itemRef deleteAt = do itemThreads <- asks timedItemThreads threadTVar_ <- atomically $ do @@ -3217,11 +3244,11 @@ startTimedItemThread user itemRef deleteAt = do tId <- mkWeakThreadId =<< deleteTimedItem user itemRef deleteAt `forkFinally` const (atomically $ TM.delete itemRef itemThreads) atomically $ writeTVar threadTVar (Just tId) -deleteTimedItem :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m () +deleteTimedItem :: User -> (ChatRef, ChatItemId) -> UTCTime -> CM () deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do ts <- liftIO getCurrentTime liftIO $ threadDelay' $ diffToMicroseconds $ diffUTCTime deleteAt ts - waitChatStartedAndActivated + lift waitChatStartedAndActivated vr <- chatVersionRange case cType of CTDirect -> do @@ -3233,33 +3260,33 @@ deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do deleteGroupCI user gInfo ci True True Nothing deletedTs >>= toView _ -> toView . CRChatError (Just user) . ChatError $ CEInternalError "bad deleteTimedItem cType" -startUpdatedTimedItemThread :: ChatMonad m => User -> ChatRef -> ChatItem c d -> ChatItem c d -> m () +startUpdatedTimedItemThread :: User -> ChatRef -> ChatItem c d -> ChatItem c d -> CM () startUpdatedTimedItemThread user chatRef ci ci' = case (chatItemTimed ci >>= timedDeleteAt', chatItemTimed ci' >>= timedDeleteAt') of (Nothing, Just deleteAt') -> startProximateTimedItemThread user (chatRef, chatItemId' ci') deleteAt' _ -> pure () -expireChatItems :: forall m. ChatMonad m => User -> Int64 -> Bool -> m () +expireChatItems :: User -> Int64 -> Bool -> CM () expireChatItems user@User {userId} ttl sync = do currentTs <- liftIO getCurrentTime vr <- chatVersionRange let expirationDate = addUTCTime (-1 * fromIntegral ttl) currentTs -- this is to keep group messages created during last 12 hours even if they're expired according to item_ts createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs - waitChatStartedAndActivated + lift waitChatStartedAndActivated contacts <- withStore' $ \db -> getUserContacts db vr user loop contacts $ processContact expirationDate - waitChatStartedAndActivated + lift waitChatStartedAndActivated groups <- withStore' $ \db -> getUserGroupDetails db vr user Nothing Nothing loop groups $ processGroup vr expirationDate createdAtCutoff where - loop :: [a] -> (a -> m ()) -> m () + loop :: [a] -> (a -> CM ()) -> CM () loop [] _ = pure () loop (a : as) process = continue $ do process a `catchChatError` (toView . CRChatError (Just user)) loop as process - continue :: m () -> m () + continue :: CM () -> CM () continue a = if sync then a @@ -3267,16 +3294,16 @@ expireChatItems user@User {userId} ttl sync = do expireFlags <- asks expireCIFlags expire <- atomically $ TM.lookup userId expireFlags when (expire == Just True) $ threadDelay 100000 >> a - processContact :: UTCTime -> Contact -> m () + processContact :: UTCTime -> Contact -> CM () processContact expirationDate ct = do - waitChatStartedAndActivated + lift waitChatStartedAndActivated filesInfo <- withStore' $ \db -> getContactExpiredFileInfo db user ct expirationDate cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate - processGroup :: (PQSupport -> VersionRangeChat) -> UTCTime -> UTCTime -> GroupInfo -> m () + processGroup :: (PQSupport -> VersionRangeChat) -> UTCTime -> UTCTime -> GroupInfo -> CM () processGroup vr expirationDate createdAtCutoff gInfo = do - waitChatStartedAndActivated + lift waitChatStartedAndActivated filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo @@ -3284,7 +3311,7 @@ expireChatItems user@User {userId} ttl sync = do membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m -processAgentMessage :: forall m. ChatMonad m => ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () +processAgentMessage :: ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> CM () processAgentMessage _ connId (DEL_RCVQ srv qId err_) = toView $ CRAgentRcvQueueDeleted (AgentConnId connId) srv (AgentQueueId qId) err_ processAgentMessage _ connId DEL_CONN = @@ -3303,13 +3330,13 @@ processAgentMessage corrId connId msg = do -- - without ACK the message delivery will be stuck, -- - with ACK message will be lost, as it failed to be saved. -- Full app restart is likely to resolve database condition and the message will be received and processed again. -critical :: ChatMonad m => m a -> m a +critical :: CM a -> CM a critical a = a `catchChatError` \case ChatErrorStore SEDBBusyError {message} -> throwError $ ChatErrorAgent (CRITICAL True message) Nothing e -> throwError e -processAgentMessageNoConn :: forall m. ChatMonad m => ACommand 'Agent 'AENone -> m () +processAgentMessageNoConn :: ACommand 'Agent 'AENone -> CM () processAgentMessageNoConn = \case CONNECT p h -> hostEvent $ CRHostConnected p h DISCONNECT p h -> hostEvent $ CRHostDisconnected p h @@ -3318,7 +3345,7 @@ processAgentMessageNoConn = \case SUSPENDED -> toView CRChatSuspended DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId where - hostEvent :: ChatResponse -> m () + hostEvent :: ChatResponse -> CM () hostEvent = whenM (asks $ hostEvents . config) . toView serverEvent srv conns nsStatus event = do chatModifyVar connNetworkStatuses $ \m -> foldl' (\m' cId -> M.insert cId nsStatus m') m connIds @@ -3330,15 +3357,15 @@ processAgentMessageNoConn = \case cs <- withStore' (`getConnectionsContacts` conns) toView $ event srv cs -processAgentMsgSndFile :: forall m. ChatMonad m => ACorrId -> SndFileId -> ACommand 'Agent 'AESndFile -> m () +processAgentMsgSndFile :: ACorrId -> SndFileId -> ACommand 'Agent 'AESndFile -> CM () processAgentMsgSndFile _corrId aFileId msg = withStore' (`getUserByASndFileId` AgentSndFileId aFileId) >>= \case Just user -> process user `catchChatError` (toView . CRChatError (Just user)) _ -> do - withAgent (`xftpDeleteSndFileInternal` aFileId) + lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) throwChatError $ CENoSndFileUser $ AgentSndFileId aFileId where - process :: User -> m () + process :: User -> CM () process user = do (ft@FileTransferMeta {fileId, xftpRedirectFor, cancelled}, sfts) <- withStore $ \db -> do fileId <- getXFTPSndFileDBId db user $ AgentSndFileId aFileId @@ -3356,7 +3383,7 @@ processAgentMsgSndFile _corrId aFileId msg = ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId case ci of Nothing -> do - withAgent (`xftpDeleteSndFileInternal` aFileId) + lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) case rfds of [] -> sendFileError "no receiver descriptions" fileId vr ft @@ -3379,7 +3406,7 @@ processAgentMsgSndFile _corrId aFileId msg = withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage user ct withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId - withAgent (`xftpDeleteSndFileInternal` aFileId) + lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do ms <- withStore' $ \db -> getGroupMembers db vr user g let rfdsMemberFTs = zip rfds $ memberFTs ms @@ -3389,7 +3416,7 @@ processAgentMsgSndFile _corrId aFileId msg = ci' <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId CIFSSndComplete getChatItemByFileId db vr user fileId - withAgent (`xftpDeleteSndFileInternal` aFileId) + lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) toView $ CRSndFileCompleteXFTP user ci' ft where memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] @@ -3401,7 +3428,7 @@ processAgentMsgSndFile _corrId aFileId msg = | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) | otherwise = Nothing useMember _ = Nothing - sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> m () + sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> CM () sendToMember (rfd, (conn, sft)) = void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> do (sndMsg, msgDeliveryId, _) <- sendDirectMemberMessage conn msg' groupId @@ -3416,7 +3443,7 @@ processAgentMsgSndFile _corrId aFileId msg = where fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text fileDescrText = safeDecodeUtf8 . strEncode - sendFileDescription :: SndFileTransfer -> ValidFileDescription 'FRecipient -> SharedMsgId -> (ChatMsgEvent 'Json -> m (SndMessage, Int64)) -> m Int64 + sendFileDescription :: SndFileTransfer -> ValidFileDescription 'FRecipient -> SharedMsgId -> (ChatMsgEvent 'Json -> CM (SndMessage, Int64)) -> CM Int64 sendFileDescription sft rfd msgId sendMsg = do let rfdText = fileDescrText rfd withStore' $ \db -> updateSndFTDescrXFTP db user sft rfdText @@ -3424,22 +3451,22 @@ processAgentMsgSndFile _corrId aFileId msg = loopSend parts where -- returns msgDeliveryId of the last file description message - loopSend :: NonEmpty FileDescr -> m Int64 + loopSend :: NonEmpty FileDescr -> CM Int64 loopSend (fileDescr :| fds) = do (_, msgDeliveryId) <- sendMsg $ XMsgFileDescr {msgId, fileDescr} case L.nonEmpty fds of Just fds' -> loopSend fds' Nothing -> pure msgDeliveryId - sendFileError :: Text -> Int64 -> (PQSupport -> VersionRangeChat) -> FileTransferMeta -> m () + sendFileError :: Text -> Int64 -> (PQSupport -> VersionRangeChat) -> FileTransferMeta -> CM () sendFileError err fileId vr ft = do logError $ "Sent file error: " <> err ci <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId CIFSSndError lookupChatItemByFileId db vr user fileId - withAgent (`xftpDeleteSndFileInternal` aFileId) + lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) toView $ CRSndFileError user ci ft err -splitFileDescr :: ChatMonad m => RcvFileDescrText -> m (NonEmpty FileDescr) +splitFileDescr :: RcvFileDescrText -> CM (NonEmpty FileDescr) splitFileDescr rfdText = do partSize <- asks $ xftpDescrPartSize . config pure $ splitParts 1 partSize rfdText @@ -3452,15 +3479,15 @@ splitFileDescr rfdText = do then fileDescr :| [] else fileDescr <| splitParts (partNo + 1) partSize rest -processAgentMsgRcvFile :: forall m. ChatMonad m => ACorrId -> RcvFileId -> ACommand 'Agent 'AERcvFile -> m () +processAgentMsgRcvFile :: ACorrId -> RcvFileId -> ACommand 'Agent 'AERcvFile -> CM () processAgentMsgRcvFile _corrId aFileId msg = withStore' (`getUserByARcvFileId` AgentRcvFileId aFileId) >>= \case Just user -> process user `catchChatError` (toView . CRChatError (Just user)) _ -> do - withAgent (`xftpDeleteRcvFile` aFileId) + lift $ withAgent' (`xftpDeleteRcvFile` aFileId) throwChatError $ CENoRcvFileUser $ AgentRcvFileId aFileId where - process :: User -> m () + process :: User -> CM () process user = do ft@RcvFileTransfer {fileId} <- withStore $ \db -> do fileId <- getXFTPRcvFileDBId db $ AgentRcvFileId aFileId @@ -3477,7 +3504,7 @@ processAgentMsgRcvFile _corrId aFileId msg = case liveRcvFileTransferPath ft of Nothing -> throwChatError $ CEInternalError "no target path for received XFTP file" Just targetPath -> do - fsTargetPath <- toFSFilePath targetPath + fsTargetPath <- lift $ toFSFilePath targetPath renameFile xftpPath fsTargetPath ci_ <- withStore $ \db -> do liftIO $ do @@ -3496,7 +3523,7 @@ processAgentMsgRcvFile _corrId aFileId msg = agentXFTPDeleteRcvFile aFileId fileId toView $ CRRcvFileError user ci e ft -processAgentMessageConn :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () +processAgentMessageConn :: (PQSupport -> VersionRangeChat) -> User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> CM () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do -- Missing connection/entity errors here will be sent to the view but not shown as CRITICAL alert, -- as in this case no need to ACK message - we can't process messages for this connection anyway. @@ -3520,7 +3547,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = UserContactConnection conn uc -> processUserContactRequest agentMessage entity conn uc where - updateConnStatus :: ConnectionEntity -> m ConnectionEntity + updateConnStatus :: ConnectionEntity -> CM ConnectionEntity updateConnStatus acEntity = case agentMsgConnStatus agentMessage of Just connStatus -> do let conn = (entityConnection acEntity) {connStatus} @@ -3535,7 +3562,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = CON _ -> Just ConnReady _ -> Nothing - processCONFpqSupport :: Connection -> PQSupport -> m Connection + processCONFpqSupport :: Connection -> PQSupport -> CM Connection processCONFpqSupport conn@Connection {connId, pqSupport = pq} pq' | pq == PQSupportOn && pq' == PQSupportOff = do let pqEnc' = CR.pqSupportToEnc pq' @@ -3546,11 +3573,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure conn | otherwise = pure conn - processINFOpqSupport :: Connection -> PQSupport -> m () + processINFOpqSupport :: Connection -> PQSupport -> CM () processINFOpqSupport Connection {pqSupport = pq} pq' = when (pq /= pq') $ messageWarning "processINFOpqSupport: unexpected pqSupport change" - processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> m () + processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> CM () processDirectMessage agentMsg connEntity conn@Connection {connId, connChatVersion, peerChatVRange, viaUserContactLink, customUserProfileId, connectionCode} = \case Nothing -> case agentMsg of CONF confId pqSupport _ connInfo -> do @@ -3569,15 +3596,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO only acknowledge without saving message? -- probably this branch is never executed, so there should be no reason -- to save message if contact hasn't been created yet - chat item isn't created anyway - withAckMessage agentConnId conn meta False $ \cmdId -> do - (_conn', _) <- saveDirectRcvMSG conn meta cmdId msgBody - pure False + withAckMessage' agentConnId meta $ + void $ saveDirectRcvMSG conn meta msgBody SENT msgId -> sentMsgDeliveryEvent conn msgId OK -> -- [async agent commands] continuation on receiving OK - withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} -> - when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) incAuthErrCounter connEntity conn err @@ -3602,11 +3627,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendXGrpMemInv hostConnId (Just directConnReq) xGrpMemIntroCont CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type" MSG msgMeta _msgFlags msgBody -> - withAckMessage agentConnId conn msgMeta True $ \cmdId -> do + withAckMessage agentConnId msgMeta True $ do let MsgMeta {pqEncryption} = msgMeta (ct', conn') <- updateContactPQRcv user ct conn pqEncryption checkIntegrityCreateItem (CDDirectRcv ct') msgMeta `catchChatError` \_ -> pure () - (conn'', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveDirectRcvMSG conn' msgMeta cmdId msgBody + (conn'', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveDirectRcvMSG conn' msgMeta msgBody let ct'' = ct' {activeConn = Just conn''} :: Contact assertDirectAllowed user MDRcv ct'' $ toCMEventTag event updateChatLock "direct message" event @@ -3636,7 +3661,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let Contact {chatSettings = ChatSettings {sendRcpts}} = ct'' pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt (toCMEventTag event) RCVD msgMeta msgRcpt -> - withAckMessage' agentConnId conn msgMeta $ + withAckMessage' agentConnId msgMeta $ directMsgReceived ct conn msgMeta msgRcpt CONF confId pqSupport _ connInfo -> do conn' <- processCONFpqSupport conn pqSupport @@ -3678,7 +3703,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ct' = ct {activeConn = Just conn'} :: Contact -- [incognito] print incognito profile used for this contact incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) - setContactNetworkStatus ct' NSConnected + lift $ setContactNetworkStatus ct' NSConnected toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile) when (directOrUsed ct') $ do createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing @@ -3744,8 +3769,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createInternalChatItem user (CDDirectRcv ct') (CIRcvConnEvent $ RCERatchetSync rss) Nothing OK -> -- [async agent commands] continuation on receiving OK - withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} -> - when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR msgId err -> do updateDirectItemStatus ct conn msgId $ agentErrToItemStatus err toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -3760,7 +3784,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () - processGroupMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> m () + processGroupMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () processGroupMessage agentMsg connEntity conn@Connection {connId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of INV (ACR _ cReq) -> withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} -> @@ -3792,7 +3816,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendGrpInvitation ct m groupLinkId toView $ CRSentGroupInvitation user gInfo ct m where - sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> m () + sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> CM () sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupMember {memberRole = userRole, memberId = userMemberId} = membership @@ -3921,7 +3945,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupInfo {groupProfile = GroupProfile {description}} = gInfo fmap (\descr -> XMsgNew $ MCSimple $ extMsgContent (MCText descr) Nothing) description | otherwise = Nothing - itemForwardEvents :: CChatItem 'CTGroup -> m [ChatMsgEvent 'Json] + itemForwardEvents :: CChatItem 'CTGroup -> CM [ChatMsgEvent 'Json] itemForwardEvents cci = case cci of (CChatItem SMDRcv ci@ChatItem {chatDir = CIGroupRcv sender, content = CIRcvMsgContent mc, file}) | not (blockedByAdmin sender) -> do @@ -3932,7 +3956,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processContentItem membership ci mc fInvDescr_ _ -> pure [] where - getRcvFileInvDescr :: CIFile 'MDRcv -> m (Maybe (FileInvitation, RcvFileDescrText)) + getRcvFileInvDescr :: CIFile 'MDRcv -> CM (Maybe (FileInvitation, RcvFileDescrText)) getRcvFileInvDescr ciFile@CIFile {fileId, fileProtocol, fileStatus} = do expired <- fileExpired if fileProtocol /= FPXFTP || fileStatus == CIFSRcvCancelled || expired @@ -3940,7 +3964,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else do rfd <- withStore $ \db -> getRcvFileDescrByRcvFileId db fileId pure $ invCompleteDescr ciFile rfd - getSndFileInvDescr :: CIFile 'MDSnd -> m (Maybe (FileInvitation, RcvFileDescrText)) + getSndFileInvDescr :: CIFile 'MDSnd -> CM (Maybe (FileInvitation, RcvFileDescrText)) getSndFileInvDescr ciFile@CIFile {fileId, fileProtocol, fileStatus} = do expired <- fileExpired if fileProtocol /= FPXFTP || fileStatus == CIFSSndCancelled || expired @@ -3950,7 +3974,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- would be best if snd file had a single rcv description for all members saved in files table rfd <- withStore $ \db -> getRcvFileDescrBySndFileId db fileId pure $ invCompleteDescr ciFile rfd - fileExpired :: m Bool + fileExpired :: CM Bool fileExpired = do ttl <- asks $ rcvFilesTTL . agentConfig . config cutoffTs <- addUTCTime (-ttl) <$> liftIO getCurrentTime @@ -3962,7 +3986,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = fInv = xftpFileInvitation fileName fileSize fInvDescr in Just (fInv, fileDescrText) | otherwise = Nothing - processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> m [ChatMsgEvent 'Json] + processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> CM [ChatMsgEvent 'Json] processContentItem sender ChatItem {meta, quotedItem} mc fInvDescr_ = if isNothing fInvDescr_ && not (msgContentHasText mc) then pure [] @@ -4011,11 +4035,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ sendDirectMemberMessage imConn (XGrpMemCon memberId) groupId _ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected" MSG msgMeta _msgFlags msgBody -> do - withAckMessage agentConnId conn msgMeta True $ \cmdId -> do + withAckMessage agentConnId msgMeta True $ do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent cmdId chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e + processEvent chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e Left e -> toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) checkSendRcpt $ rights aChatMsgs -- currently only a single message is forwarded @@ -4026,9 +4050,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where aChatMsgs = parseChatMessages msgBody brokerTs = metaBrokerTs msgMeta - processEvent :: MsgEncodingI e => CommandId -> ChatMessage e -> m () - processEvent cmdId chatMsg = do - (m', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m conn msgMeta cmdId msgBody chatMsg + processEvent :: MsgEncodingI e => ChatMessage e -> CM () + processEvent chatMsg = do + (m', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m conn msgMeta msgBody chatMsg updateChatLock "groupMessage" event case event of XMsgNew mc -> memberCanSend m' $ newGroupContentMessage gInfo m' mc msg brokerTs False @@ -4060,7 +4084,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m') probe BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta _ -> messageError $ "unsupported message: " <> T.pack (show event) - checkSendRcpt :: [AChatMessage] -> m Bool + checkSendRcpt :: [AChatMessage] -> CM Bool checkSendRcpt aMsgs = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo @@ -4071,7 +4095,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where aChatMsgHasReceipt (ACMsg _ ChatMessage {chatMsgEvent}) = hasDeliveryReceipt (toCMEventTag chatMsgEvent) - forwardMsg_ :: MsgEncodingI e => ChatMessage e -> m () + forwardMsg_ :: MsgEncodingI e => ChatMessage e -> CM () forwardMsg_ chatMsg = forM_ (forwardedGroupMsg chatMsg) $ \chatMsg' -> do ChatConfig {highlyAvailable} <- asks config @@ -4088,7 +4112,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (null ms) . void $ sendGroupMessage' user gInfo ms msg RCVD msgMeta msgRcpt -> - withAckMessage' agentConnId conn msgMeta $ + withAckMessage' agentConnId msgMeta $ groupMsgReceived gInfo m conn msgMeta msgRcpt SENT msgId -> do sentMsgDeliveryEvent conn msgId @@ -4128,8 +4152,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createInternalChatItem user (CDGroupRcv gInfo m') (CIRcvConnEvent $ RCERatchetSync rss) Nothing OK -> -- [async agent commands] continuation on receiving OK - withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} -> - when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR msgId err -> do withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) $ agentErrToItemStatus err -- group errors are silenced to reduce load on UI event log @@ -4174,7 +4197,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = r n'' = Just (ci, CIRcvDecryptionError mde n'') mdeUpdatedCI _ _ = Nothing - processSndFileConn :: ACommand 'Agent e -> ConnectionEntity -> Connection -> SndFileTransfer -> m () + processSndFileConn :: ACommand 'Agent e -> ConnectionEntity -> Connection -> SndFileTransfer -> CM () processSndFileConn agentMsg connEntity conn ft@SndFileTransfer {fileId, fileName, fileStatus} = case agentMsg of -- SMP CONF for SndFileConnection happens for direct file protocol @@ -4211,17 +4234,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = lookupChatItemByFileId db vr user fileId toView $ CRSndFileRcvCancelled user ci ft _ -> throwChatError $ CEFileSend fileId err - MSG meta _ _ -> withAckMessage' agentConnId conn meta $ pure () + MSG meta _ _ -> withAckMessage' agentConnId meta $ pure () OK -> -- [async agent commands] continuation on receiving OK - withCompletedCommand conn agentMsg $ \_cmdData -> pure () + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () - processRcvFileConn :: ACommand 'Agent e -> ConnectionEntity -> Connection -> RcvFileTransfer -> m () + processRcvFileConn :: ACommand 'Agent e -> ConnectionEntity -> Connection -> RcvFileTransfer -> CM () processRcvFileConn agentMsg connEntity conn ft@RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}, grpMemberId} = case agentMsg of INV (ACR _ cReq) -> @@ -4260,7 +4283,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = parseFileChunk msgBody >>= receiveFileChunk ft (Just conn) meta OK -> -- [async agent commands] continuation on receiving OK - withCompletedCommand conn agentMsg $ \_cmdData -> pure () + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) incAuthErrCounter connEntity conn err @@ -4270,7 +4293,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () - receiveFileChunk :: RcvFileTransfer -> Maybe Connection -> MsgMeta -> FileChunk -> m () + receiveFileChunk :: RcvFileTransfer -> Maybe Connection -> MsgMeta -> FileChunk -> CM () receiveFileChunk ft@RcvFileTransfer {fileId, chunkSize} conn_ meta@MsgMeta {recipient = (msgId, _), integrity} = \case FileChunkCancel -> unless (rcvFileCompleteOrCancelled ft) $ do @@ -4287,7 +4310,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = RcvChunkOk -> if B.length chunk /= fromInteger chunkSize then badRcvFileChunk ft "incorrect chunk size" - else ack $ appendFileChunk ft chunkNo chunk False + else withAckMessage' agentConnId meta $ appendFileChunk ft chunkNo chunk False RcvChunkFinal -> if B.length chunk > fromInteger chunkSize then badRcvFileChunk ft "incorrect chunk size" @@ -4301,14 +4324,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = getChatItemByFileId db vr user fileId toView $ CRRcvFileComplete user ci forM_ conn_ $ \conn -> deleteAgentConnectionAsync user (aConnId conn) - RcvChunkDuplicate -> ack $ pure () + RcvChunkDuplicate -> withAckMessage' agentConnId meta $ pure () RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo - where - ack a = case conn_ of - Just conn -> withAckMessage' agentConnId conn meta a - Nothing -> a - processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> m () + processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> CM () processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of REQ invId pqSupport _ connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo @@ -4326,7 +4345,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> m () + profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> CM () profileContactRequest invId chatVRange p xContactId_ reqPQSup = do withStore (\db -> createOrUpdateContactRequest db vr user userContactLinkId invId chatVRange p xContactId_ reqPQSup) >>= \case CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact @@ -4356,12 +4375,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRAcceptingGroupJoinRequest user gInfo ct _ -> toView $ CRReceivedContactRequest user cReq - memberCanSend :: GroupMember -> m () -> m () + memberCanSend :: GroupMember -> CM () -> CM () memberCanSend GroupMember {memberRole} a | memberRole <= GRObserver = messageError "member is not allowed to send messages" | otherwise = a - incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> m () + incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> CM () incAuthErrCounter connEntity conn err = do case err of SMP SMP.AUTH -> do @@ -4370,14 +4389,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRConnectionDisabled connEntity _ -> pure () - updateChatLock :: MsgEncodingI enc => String -> ChatMsgEvent enc -> m () + updateChatLock :: MsgEncodingI enc => String -> ChatMsgEvent enc -> CM () updateChatLock name event = do l <- asks chatLock atomically $ tryReadTMVar l >>= mapM_ (swapTMVar l . (<> s)) where s = " " <> name <> "=" <> B.unpack (strEncode $ toCMEventTag event) - withCompletedCommand :: forall e. AEntityI e => Connection -> ACommand 'Agent e -> (CommandData -> m ()) -> m () + -- TODO v5.7 / v6.0 - together with deprecating old group protocol establishing direct connections? + -- we could save command records only for agent APIs we process continuations for (INV) + withCompletedCommand :: forall e. AEntityI e => Connection -> ACommand 'Agent e -> (CommandData -> CM ()) -> CM () withCompletedCommand Connection {connId} agentMsg action = do let agentMsgTag = APCT (sAEntity @e) $ aCommandTag agentMsg cmdData_ <- withStore' $ \db -> getCommandDataByCorrId db user corrId @@ -4394,38 +4415,29 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateCommandStatus db user cmdId CSError throwChatError . CEAgentCommandError $ msg - createAckCmd :: Connection -> m CommandId - createAckCmd Connection {connId} = do - withStore' $ \db -> createCommand db user (Just connId) CFAckMessage + withAckMessage' :: ConnId -> MsgMeta -> CM () -> CM () + withAckMessage' cId msgMeta action = do + withAckMessage cId msgMeta False $ action $> False - withAckMessage' :: ConnId -> Connection -> MsgMeta -> m () -> m () - withAckMessage' cId conn msgMeta action = do - withAckMessage cId conn msgMeta False $ \_cmdId -> action $> False - - withAckMessage :: ConnId -> Connection -> MsgMeta -> Bool -> (CommandId -> m Bool) -> m () - withAckMessage cId conn msgMeta showCritical action = do - cmdId <- createAckCmd conn `catchChatError` \e -> throwError $ ChatErrorAgent (CRITICAL True $ show e) Nothing - -- [async agent commands] command should be asynchronous, continuation is ackMsgDeliveryEvent + withAckMessage :: ConnId -> MsgMeta -> Bool -> CM Bool -> CM () + withAckMessage cId msgMeta showCritical action = + -- [async agent commands] command should be asynchronous -- TODO catching error and sending ACK after an error, particularly if it is a database error, will result in the message not processed (and no notification to the user). -- Possible solutions are: -- 1) retry processing several times -- 2) stabilize database -- 3) show screen of death to the user asking to restart - tryChatError (action cmdId) >>= \case - Right withRcpt -> ackMsg cId cmdId msgMeta $ if withRcpt then Just "" else Nothing + tryChatError action >>= \case + Right withRcpt -> ackMsg msgMeta $ if withRcpt then Just "" else Nothing -- If showCritical is True, then these errors don't result in ACK and show user visible alert -- This prevents losing the message that failed to be processed. Left (ChatErrorStore SEDBBusyError {message}) | showCritical -> throwError $ ChatErrorAgent (CRITICAL True message) Nothing - Left e -> ackMsg cId cmdId msgMeta Nothing >> throwError e + Left e -> ackMsg msgMeta Nothing >> throwError e + where + ackMsg :: MsgMeta -> Maybe MsgReceiptInfo -> CM () + ackMsg MsgMeta {recipient = (msgId, _)} rcpt = withAgent $ \a -> ackMessageAsync a "" cId msgId rcpt - ackMsg :: ConnId -> CommandId -> MsgMeta -> Maybe MsgReceiptInfo -> m () - ackMsg cId cmdId MsgMeta {recipient = (msgId, _)} rcpt = withAgent $ \a -> ackMessageAsync a (aCorrId cmdId) cId msgId rcpt - - ackMsgDeliveryEvent :: Connection -> CommandId -> m () - ackMsgDeliveryEvent Connection {connId} ackCmdId = - withStore' $ \db -> updateRcvMsgDeliveryStatus db connId ackCmdId MDSRcvAcknowledged - - sentMsgDeliveryEvent :: Connection -> AgentMsgId -> m () + sentMsgDeliveryEvent :: Connection -> AgentMsgId -> CM () sentMsgDeliveryEvent Connection {connId} msgId = withStore' $ \db -> updateSndMsgDeliveryStatus db connId msgId MDSSndSent @@ -4433,28 +4445,28 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = agentErrToItemStatus (SMP AUTH) = CISSndErrorAuth agentErrToItemStatus err = CISSndError . T.unpack . safeDecodeUtf8 $ strEncode err - badRcvFileChunk :: RcvFileTransfer -> String -> m () + badRcvFileChunk :: RcvFileTransfer -> String -> CM () badRcvFileChunk ft err = unless (rcvFileCompleteOrCancelled ft) $ do cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) throwChatError $ CEFileRcvChunk err - memberConnectedChatItem :: GroupInfo -> GroupMember -> m () + memberConnectedChatItem :: GroupInfo -> GroupMember -> CM () memberConnectedChatItem gInfo m = -- ts should be broker ts but we don't have it for CON createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupEvent RGEMemberConnected) Nothing - groupDescriptionChatItem :: GroupInfo -> GroupMember -> Text -> m () + groupDescriptionChatItem :: GroupInfo -> GroupMember -> Text -> CM () groupDescriptionChatItem gInfo m descr = createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvMsgContent $ MCText descr) Nothing - notifyMemberConnected :: GroupInfo -> GroupMember -> Maybe Contact -> m () + notifyMemberConnected :: GroupInfo -> GroupMember -> Maybe Contact -> CM () notifyMemberConnected gInfo m ct_ = do memberConnectedChatItem gInfo m - mapM_ (`setContactNetworkStatus` NSConnected) ct_ + lift $ mapM_ (`setContactNetworkStatus` NSConnected) ct_ toView $ CRConnectedToGroupMember user gInfo m ct_ - probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> m () + probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> CM () probeMatchingContactsAndMembers ct connectedIncognito doProbeContacts = do gVar <- asks random contactMerge <- readTVarIO =<< asks contactMergeEnabled @@ -4474,10 +4486,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendProbeHashes (cs <> ms) probe probeId else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32) where - sendProbe :: Probe -> m () + sendProbe :: Probe -> CM () sendProbe probe = void . sendDirectContactMessage user ct $ XInfoProbe probe - probeMatchingMemberContact :: GroupMember -> IncognitoEnabled -> m () + probeMatchingMemberContact :: GroupMember -> IncognitoEnabled -> CM () probeMatchingMemberContact GroupMember {activeConn = Nothing} _ = pure () probeMatchingMemberContact m@GroupMember {groupId, activeConn = Just conn} connectedIncognito = do gVar <- asks random @@ -4490,15 +4502,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendProbeHashes cs probe probeId else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32) where - sendProbe :: Probe -> m () + sendProbe :: Probe -> CM () sendProbe probe = void $ sendDirectMemberMessage conn (XInfoProbe probe) groupId - sendProbeHashes :: [ContactOrMember] -> Probe -> Int64 -> m () + sendProbeHashes :: [ContactOrMember] -> Probe -> Int64 -> CM () sendProbeHashes cgms probe probeId = forM_ cgms $ \cgm -> sendProbeHash cgm `catchChatError` \_ -> pure () where probeHash = ProbeHash $ C.sha256Hash (unProbe probe) - sendProbeHash :: ContactOrMember -> m () + sendProbeHash :: ContactOrMember -> CM () sendProbeHash cgm@(COMContact c) = do void . sendDirectContactMessage user c $ XInfoProbeCheck probeHash withStore' $ \db -> createSentProbeHash db userId probeId cgm @@ -4508,13 +4520,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ sendDirectMemberMessage conn (XInfoProbeCheck probeHash) groupId withStore' $ \db -> createSentProbeHash db userId probeId cgm - messageWarning :: Text -> m () + messageWarning :: Text -> CM () messageWarning = toView . CRMessageError user "warning" - messageError :: Text -> m () + messageError :: Text -> CM () messageError = toView . CRMessageError user "error" - newContentMessage :: Contact -> MsgContainer -> RcvMessage -> MsgMeta -> m () + newContentMessage :: Contact -> MsgContainer -> RcvMessage -> MsgMeta -> CM () newContentMessage ct@Contact {contactUsed} mc msg@RcvMessage {sharedMsgId_} msgMeta = do unless contactUsed $ withStore' $ \db -> updateContactUsed db user ct let ExtMsgContent content fInv_ _ _ = mcExtMsgContent mc @@ -4541,36 +4553,39 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_ toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}) - autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> m () + autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM () autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do ChatConfig {autoAcceptFileSize = sz} <- asks config when (sz > fileSize) $ receiveFile' user ft Nothing Nothing >>= toView - messageFileDescription :: Contact -> SharedMsgId -> FileDescr -> m () - messageFileDescription Contact {contactId} sharedMsgId fileDescr = do + messageFileDescription :: Contact -> SharedMsgId -> FileDescr -> CM () + messageFileDescription ct@Contact {contactId} sharedMsgId fileDescr = do fileId <- withStore $ \db -> getFileIdBySharedMsgId db userId contactId sharedMsgId - processFDMessage fileId fileDescr + processFDMessage (CDDirectRcv ct) sharedMsgId fileId fileDescr - groupMessageFileDescription :: GroupInfo -> GroupMember -> SharedMsgId -> FileDescr -> m () - groupMessageFileDescription GroupInfo {groupId} _m sharedMsgId fileDescr = do + groupMessageFileDescription :: GroupInfo -> GroupMember -> SharedMsgId -> FileDescr -> CM () + groupMessageFileDescription g@GroupInfo {groupId} m sharedMsgId fileDescr = do fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId - processFDMessage fileId fileDescr + processFDMessage (CDGroupRcv g m) sharedMsgId fileId fileDescr - processFDMessage :: FileTransferId -> FileDescr -> m () - processFDMessage fileId fileDescr = do + processFDMessage :: ChatTypeQuotable c => ChatDirection c 'MDRcv -> SharedMsgId -> FileTransferId -> FileDescr -> CM () + processFDMessage cd sharedMsgId fileId fileDescr = do ft <- withStore $ \db -> getRcvFileTransfer db user fileId unless (rcvFileCompleteOrCancelled ft) $ do - (rfd, RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs}) <- withStore $ \db -> do + (rfd@RcvFileDescr {fileDescrComplete}, ft'@RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs}) <- withStore $ \db -> do rfd <- appendRcvFD db userId fileId fileDescr -- reading second time in the same transaction as appending description -- to prevent race condition with accept ft' <- getRcvFileTransfer db user fileId pure (rfd, ft') + when fileDescrComplete $ do + ci <- withStore $ \db -> getAChatItemBySharedMsgId db user cd sharedMsgId + toView $ CRRcvFileDescrReady user ci ft' rfd case (fileStatus, xftpRcvFile) of (RFSAccepted _, Just XFTPRcvFile {}) -> receiveViaCompleteFD user fileId rfd cryptoArgs _ -> pure () - processFileInvitation :: Maybe FileInvitation -> MsgContent -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> ExceptT StoreError IO RcvFileTransfer) -> m (Maybe (RcvFileTransfer, CIFile 'MDRcv)) + processFileInvitation :: Maybe FileInvitation -> MsgContent -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> ExceptT StoreError IO RcvFileTransfer) -> CM (Maybe (RcvFileTransfer, CIFile 'MDRcv)) processFileInvitation fInv_ mc createRcvFT = forM fInv_ $ \fInv@FileInvitation {fileName, fileSize} -> do ChatConfig {fileChunkSize} <- asks config inline <- receiveInlineMode fInv (Just mc) fileChunkSize @@ -4588,7 +4603,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = fileSource = (`CryptoFile` cryptoArgs) <$> filePath pure (ft', CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol}) - messageUpdate :: Contact -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> m () + messageUpdate :: Contact -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> CM () messageUpdate ct@Contact {contactId} sharedMsgId mc msg@RcvMessage {msgId} msgMeta ttl live_ = do updateRcvChatItem `catchCINotFound` \_ -> do -- This patches initial sharedMsgId into chat item when locally deleted chat item @@ -4621,7 +4636,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else toView $ CRChatItemNotChanged user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) _ -> messageError "x.msg.update: contact attempted invalid message update" - messageDelete :: Contact -> SharedMsgId -> RcvMessage -> MsgMeta -> m () + messageDelete :: Contact -> SharedMsgId -> RcvMessage -> MsgMeta -> CM () messageDelete ct@Contact {contactId} sharedMsgId RcvMessage {msgId} msgMeta = do deleteRcvChatItem `catchCINotFound` (toView . CRChatItemDeletedNotFound user ct) where @@ -4635,7 +4650,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else markDirectCIDeleted user ct ci msgId False brokerTs >>= toView SMDSnd -> messageError "x.msg.del: contact attempted invalid message delete" - directMsgReaction :: Contact -> SharedMsgId -> MsgReaction -> Bool -> RcvMessage -> MsgMeta -> m () + directMsgReaction :: Contact -> SharedMsgId -> MsgReaction -> Bool -> RcvMessage -> MsgMeta -> CM () directMsgReaction ct sharedMsgId reaction add RcvMessage {msgId} MsgMeta {broker = (_, brokerTs)} = do when (featureAllowed SCFReactions forContact ct) $ do rs <- withStore' $ \db -> getDirectReactions db ct sharedMsgId False @@ -4656,7 +4671,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else pure Nothing mapM_ toView cr_ - groupMsgReaction :: GroupInfo -> GroupMember -> SharedMsgId -> MemberId -> MsgReaction -> Bool -> RcvMessage -> UTCTime -> m () + groupMsgReaction :: GroupInfo -> GroupMember -> SharedMsgId -> MemberId -> MsgReaction -> Bool -> RcvMessage -> UTCTime -> CM () groupMsgReaction g@GroupInfo {groupId} m sharedMsgId itemMemberId reaction add RcvMessage {msgId} brokerTs = do when (groupFeatureAllowed SGFReactions g) $ do rs <- withStore' $ \db -> getGroupReactions db g m itemMemberId sharedMsgId False @@ -4680,13 +4695,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactionAllowed :: Bool -> MsgReaction -> [MsgReaction] -> Bool reactionAllowed add reaction rs = (reaction `elem` rs) /= add && not (add && length rs >= maxMsgReactions) - catchCINotFound :: m a -> (SharedMsgId -> m a) -> m a + catchCINotFound :: CM a -> (SharedMsgId -> CM a) -> CM a catchCINotFound f handle = f `catchChatError` \case ChatErrorStore (SEChatItemSharedMsgIdNotFound sharedMsgId) -> handle sharedMsgId e -> throwError e - newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> m () + newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> CM () newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded | blockedByAdmin m = createBlockedByAdmin | isVoice content && not (groupFeatureAllowed SGFVoice gInfo) = rejected GFVoice @@ -4737,7 +4752,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getGroupCIReactions db gInfo memberId sharedMsgId) sharedMsgId_ groupMsgToView gInfo ci' {reactions} - groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> m () + groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM () groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_ = updateRcvChatItem `catchCINotFound` \_ -> do -- This patches initial sharedMsgId into chat item when locally deleted chat item @@ -4773,7 +4788,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else messageError "x.msg.update: group member attempted to update a message of another member" _ -> messageError "x.msg.update: group member attempted invalid message update" - groupMessageDelete :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe MemberId -> RcvMessage -> UTCTime -> m () + groupMessageDelete :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe MemberId -> RcvMessage -> UTCTime -> CM () groupMessageDelete gInfo@GroupInfo {groupId, membership} m@GroupMember {memberId, memberRole = senderRole} sharedMsgId sndMemberId_ RcvMessage {msgId} brokerTs = do let msgMemberId = fromMaybe memberId sndMemberId_ withStore' (\db -> runExceptT $ getGroupMemberCIBySharedMsgId db user groupId msgMemberId sharedMsgId) >>= \case @@ -4787,7 +4802,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | senderRole < GRAdmin -> messageError $ "x.msg.del: message not found, message of another member with insufficient member permissions, " <> tshow e | otherwise -> withStore' $ \db -> createCIModeration db gInfo m msgMemberId sharedMsgId msgId brokerTs where - deleteMsg :: MsgDirectionI d => GroupMember -> ChatItem 'CTGroup d -> m () + deleteMsg :: MsgDirectionI d => GroupMember -> ChatItem 'CTGroup d -> CM () deleteMsg mem ci = case sndMemberId_ of Just sndMemberId | sameMemberId sndMemberId mem -> checkRole mem $ delete ci (Just m) >>= toView @@ -4797,13 +4812,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | senderRole < GRAdmin || senderRole < memberRole = messageError "x.msg.del: message of another member with insufficient member permissions" | otherwise = a - delete :: MsgDirectionI d => ChatItem 'CTGroup d -> Maybe GroupMember -> m ChatResponse + delete :: MsgDirectionI d => ChatItem 'CTGroup d -> Maybe GroupMember -> CM ChatResponse delete ci byGroupMember | groupFeatureAllowed SGFFullDelete gInfo = deleteGroupCI user gInfo ci False False byGroupMember brokerTs | otherwise = markGroupCIDeleted user gInfo ci msgId False byGroupMember brokerTs -- TODO remove once XFile is discontinued - processFileInvitation' :: Contact -> FileInvitation -> RcvMessage -> MsgMeta -> m () + processFileInvitation' :: Contact -> FileInvitation -> RcvMessage -> MsgMeta -> CM () processFileInvitation' ct fInv@FileInvitation {fileName, fileSize} msg@RcvMessage {sharedMsgId_} msgMeta = do ChatConfig {fileChunkSize} <- asks config inline <- receiveInlineMode fInv Nothing fileChunkSize @@ -4816,7 +4831,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = brokerTs = metaBrokerTs msgMeta -- TODO remove once XFile is discontinued - processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> UTCTime -> m () + processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> UTCTime -> CM () processGroupFileInvitation' gInfo m fInv@FileInvitation {fileName, fileSize} msg@RcvMessage {sharedMsgId_} brokerTs = do ChatConfig {fileChunkSize} <- asks config inline <- receiveInlineMode fInv Nothing fileChunkSize @@ -4832,7 +4847,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | showMessages (memberSettings m) = pure ci | otherwise = blockedCI - receiveInlineMode :: FileInvitation -> Maybe MsgContent -> Integer -> m (Maybe InlineFileMode) + receiveInlineMode :: FileInvitation -> Maybe MsgContent -> Integer -> CM (Maybe InlineFileMode) receiveInlineMode FileInvitation {fileSize, fileInline, fileDescr} mc_ chSize = case (fileInline, fileDescr) of (Just mode, Nothing) -> do InlineFilesConfig {receiveChunks, receiveInstant} <- asks $ inlineFiles . config @@ -4841,7 +4856,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = inline' receiveInstant = if mode == IFMOffer || (receiveInstant && maybe False isVoice mc_) then fileInline else Nothing _ -> pure Nothing - xFileCancel :: Contact -> SharedMsgId -> m () + xFileCancel :: Contact -> SharedMsgId -> CM () xFileCancel Contact {contactId} sharedMsgId = do fileId <- withStore $ \db -> getFileIdBySharedMsgId db userId contactId sharedMsgId ft <- withStore (\db -> getRcvFileTransfer db user fileId) @@ -4850,7 +4865,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci <- withStore $ \db -> getChatItemByFileId db vr user fileId toView $ CRRcvFileSndCancelled user ci ft - xFileAcptInv :: Contact -> SharedMsgId -> Maybe ConnReqInvitation -> String -> m () + xFileAcptInv :: Contact -> SharedMsgId -> Maybe ConnReqInvitation -> String -> CM () xFileAcptInv ct sharedMsgId fileConnReq_ fName = do fileId <- withStore $ \db -> getDirectFileIdBySharedMsgId db user ct sharedMsgId (AChatItem _ _ _ ci) <- withStore $ \db -> getChatItemByFileId db vr user fileId @@ -4878,7 +4893,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (messageError "x.file.acpt.inv: fileSize is bigger than allowed to send inline") else messageError "x.file.acpt.inv: fileName is different from expected" - assertSMPAcceptNotProhibited :: ChatItem c d -> m () + assertSMPAcceptNotProhibited :: ChatItem c d -> CM () assertSMPAcceptNotProhibited ChatItem {file = Just CIFile {fileId, fileProtocol}, content} | fileProtocol == FPXFTP && not (imageOrVoice content) = throwChatError $ CEFallbackToSMPProhibited fileId | otherwise = pure () @@ -4889,7 +4904,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = imageOrVoice _ = False assertSMPAcceptNotProhibited _ = pure () - checkSndInlineFTComplete :: Connection -> AgentMsgId -> m () + checkSndInlineFTComplete :: Connection -> AgentMsgId -> CM () checkSndInlineFTComplete conn agentMsgId = do sft_ <- withStore' $ \db -> getSndFTViaMsgDelivery db user conn agentMsgId forM_ sft_ $ \sft@SndFileTransfer {fileId} -> do @@ -4903,24 +4918,24 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRSndFileCompleteXFTP user ci ft _ -> toView $ CRSndFileComplete user ci sft - allowSendInline :: Integer -> Maybe InlineFileMode -> m Bool + allowSendInline :: Integer -> Maybe InlineFileMode -> CM Bool allowSendInline fileSize = \case Just IFMOffer -> do ChatConfig {fileChunkSize, inlineFiles} <- asks config pure $ fileSize <= fileChunkSize * offerChunks inlineFiles _ -> pure False - bFileChunk :: Contact -> SharedMsgId -> FileChunk -> MsgMeta -> m () + bFileChunk :: Contact -> SharedMsgId -> FileChunk -> MsgMeta -> CM () bFileChunk ct sharedMsgId chunk meta = do ft <- withStore $ \db -> getDirectFileIdBySharedMsgId db user ct sharedMsgId >>= getRcvFileTransfer db user receiveInlineChunk ft chunk meta - bFileChunkGroup :: GroupInfo -> SharedMsgId -> FileChunk -> MsgMeta -> m () + bFileChunkGroup :: GroupInfo -> SharedMsgId -> FileChunk -> MsgMeta -> CM () bFileChunkGroup GroupInfo {groupId} sharedMsgId chunk meta = do ft <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId >>= getRcvFileTransfer db user receiveInlineChunk ft chunk meta - receiveInlineChunk :: RcvFileTransfer -> FileChunk -> MsgMeta -> m () + receiveInlineChunk :: RcvFileTransfer -> FileChunk -> MsgMeta -> CM () receiveInlineChunk RcvFileTransfer {fileId, fileStatus = RFSNew} FileChunk {chunkNo} _ | chunkNo == 1 = throwChatError $ CEInlineFileProhibited fileId | otherwise = pure () @@ -4930,7 +4945,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> pure () receiveFileChunk ft Nothing meta chunk - xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> m () + xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> CM () xFileCancelGroup GroupInfo {groupId} GroupMember {groupMemberId, memberId} sharedMsgId = do fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId CChatItem msgDir ChatItem {chatDir} <- withStore $ \db -> getGroupChatItemBySharedMsgId db user groupId groupMemberId sharedMsgId @@ -4946,7 +4961,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else messageError "x.file.cancel: group member attempted to cancel file of another member" -- shouldn't happen now that query includes group member id (SMDSnd, _) -> messageError "x.file.cancel: group member attempted invalid file cancel" - xFileAcptInvGroup :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe ConnReqInvitation -> String -> m () + xFileAcptInvGroup :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe ConnReqInvitation -> String -> CM () xFileAcptInvGroup GroupInfo {groupId} m@GroupMember {activeConn} sharedMsgId fileConnReq_ fName = do fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId (AChatItem _ _ _ ci) <- withStore $ \db -> getChatItemByFileId db vr user fileId @@ -4976,11 +4991,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> messageError "x.file.acpt.inv: member connection is not active" else messageError "x.file.acpt.inv: fileName is different from expected" - groupMsgToView :: GroupInfo -> ChatItem 'CTGroup 'MDRcv -> m () + groupMsgToView :: GroupInfo -> ChatItem 'CTGroup 'MDRcv -> CM () groupMsgToView gInfo ci = toView $ CRNewChatItem user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci) - processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> m () + processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM () processGroupInvitation ct inv msg msgMeta = do let Contact {localDisplayName = c, activeConn} = ct GroupInvitation {fromMember = (MemberIdRole fromMemId fromRole), invitedMember = (MemberIdRole memId memRole), connRequest, groupLinkId} = inv @@ -5013,15 +5028,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sameGroupLinkId (Just gli) (Just gli') = gli == gli' sameGroupLinkId _ _ = False - checkIntegrityCreateItem :: forall c. ChatTypeI c => ChatDirection c 'MDRcv -> MsgMeta -> m () + checkIntegrityCreateItem :: forall c. ChatTypeI c => ChatDirection c 'MDRcv -> MsgMeta -> CM () checkIntegrityCreateItem cd MsgMeta {integrity, broker = (_, brokerTs)} = case integrity of MsgOk -> pure () MsgError e -> createInternalChatItem user cd (CIRcvIntegrityError e) (Just brokerTs) - xInfo :: Contact -> Profile -> m () + xInfo :: Contact -> Profile -> CM () xInfo c p' = void $ processContactProfileUpdate c p' True - xDirectDel :: Contact -> RcvMessage -> MsgMeta -> m () + xDirectDel :: Contact -> RcvMessage -> MsgMeta -> CM () xDirectDel c msg msgMeta = if directOrUsed c then do @@ -5041,7 +5056,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where brokerTs = metaBrokerTs msgMeta - processContactProfileUpdate :: Contact -> Profile -> Bool -> m Contact + processContactProfileUpdate :: Contact -> Profile -> Bool -> CM Contact processContactProfileUpdate c@Contact {profile = lp} p' createItems | p /= p' = do c' <- withStore $ \db -> @@ -5052,7 +5067,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateContactProfile db user c' p' when (directOrUsed c' && createItems) $ do createProfileUpdatedItem c' - createRcvFeatureItems user c c' + lift $ createRcvFeatureItems user c c' toView $ CRContactUpdated user c c' pure c' | otherwise = @@ -5082,10 +5097,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Profile {displayName = n, fullName = fn, image = i, contactLink = cl} = p Profile {displayName = n', fullName = fn', image = i', contactLink = cl'} = p' - xInfoMember :: GroupInfo -> GroupMember -> Profile -> m () + xInfoMember :: GroupInfo -> GroupMember -> Profile -> CM () xInfoMember gInfo m p' = void $ processMemberProfileUpdate gInfo m p' True - xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> m () + xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM () xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId if viaGroupLink && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived @@ -5096,7 +5111,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = probeMatchingMemberContact m' connectedIncognito else messageError "x.grp.link.mem error: invalid group link host profile update" - processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> m GroupMember + processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> CM GroupMember processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = case memberContactId of @@ -5129,20 +5144,20 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' createInternalChatItem user (CDGroupRcv gInfo m') ciContent Nothing - createFeatureEnabledItems :: Contact -> m () + createFeatureEnabledItems :: Contact -> CM () createFeatureEnabledItems ct@Contact {mergedPreferences} = forM_ allChatFeatures $ \(ACF f) -> do let state = featureState $ getContactUserPreference f mergedPreferences createInternalChatItem user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing - createGroupFeatureItems :: GroupInfo -> GroupMember -> m () + createGroupFeatureItems :: GroupInfo -> GroupMember -> CM () createGroupFeatureItems g@GroupInfo {fullGroupPreferences} m = forM_ allGroupFeatures $ \(AGF f) -> do let p = getGroupPreference f fullGroupPreferences (_, param) = groupFeatureState p createInternalChatItem user (CDGroupRcv g m) (CIRcvGroupFeature (toGroupFeature f) (toGroupPreference p) param) Nothing - xInfoProbe :: ContactOrMember -> Probe -> m () + xInfoProbe :: ContactOrMember -> Probe -> CM () xInfoProbe cgm2 probe = do contactMerge <- readTVarIO =<< asks contactMergeEnabled -- [incognito] unless connected incognito @@ -5151,14 +5166,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let cgm1s' = filter (not . contactOrMemberIncognito) cgm1s probeMatches cgm1s' cgm2 where - probeMatches :: [ContactOrMember] -> ContactOrMember -> m () + probeMatches :: [ContactOrMember] -> ContactOrMember -> CM () probeMatches [] _ = pure () probeMatches (cgm1' : cgm1s') cgm2' = do cgm2''_ <- probeMatch cgm1' cgm2' probe `catchChatError` \_ -> pure (Just cgm2') let cgm2'' = fromMaybe cgm2' cgm2''_ probeMatches cgm1s' cgm2'' - xInfoProbeCheck :: ContactOrMember -> ProbeHash -> m () + xInfoProbeCheck :: ContactOrMember -> ProbeHash -> CM () xInfoProbeCheck cgm1 probeHash = do contactMerge <- readTVarIO =<< asks contactMergeEnabled -- [incognito] unless connected incognito @@ -5168,7 +5183,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (contactOrMemberIncognito cgm2) . void $ probeMatch cgm1 cgm2 probe - probeMatch :: ContactOrMember -> ContactOrMember -> Probe -> m (Maybe ContactOrMember) + probeMatch :: ContactOrMember -> ContactOrMember -> Probe -> CM (Maybe ContactOrMember) probeMatch cgm1 cgm2 probe = case cgm1 of COMContact c1@Contact {contactId = cId1, profile = p1} -> @@ -5193,7 +5208,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> messageWarning "probeMatch ignored: profiles don't match or member already has contact or member not current" >> pure Nothing COMGroupMember _ -> messageWarning "probeMatch ignored: members are not matched with members" >> pure Nothing - xInfoProbeOk :: ContactOrMember -> Probe -> m () + xInfoProbeOk :: ContactOrMember -> Probe -> CM () xInfoProbeOk cgm1 probe = do cgm2 <- withStore' $ \db -> matchSentProbe db vr user cgm1 probe case cgm1 of @@ -5215,7 +5230,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> pure () -- to party accepting call - xCallInv :: Contact -> CallId -> CallInvitation -> RcvMessage -> MsgMeta -> m () + xCallInv :: Contact -> CallId -> CallInvitation -> RcvMessage -> MsgMeta -> CM () xCallInv ct@Contact {contactId} callId CallInvitation {callType, callDhPubKey} msg@RcvMessage {sharedMsgId_} msgMeta = do if featureAllowed SCFCalls forContact ct then do @@ -5243,7 +5258,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) -- to party initiating call - xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> m () + xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM () xCallOffer ct callId CallOffer {callType, rtcSession, callDhPubKey} msg = do msgCurrentCall ct callId "x.call.offer" msg $ \call -> case callState call of @@ -5258,7 +5273,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure (Just call, Nothing) -- to party accepting call - xCallAnswer :: Contact -> CallId -> CallAnswer -> RcvMessage -> m () + xCallAnswer :: Contact -> CallId -> CallAnswer -> RcvMessage -> CM () xCallAnswer ct callId CallAnswer {rtcSession} msg = do msgCurrentCall ct callId "x.call.answer" msg $ \call -> case callState call of @@ -5271,7 +5286,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure (Just call, Nothing) -- to any call party - xCallExtra :: Contact -> CallId -> CallExtraInfo -> RcvMessage -> m () + xCallExtra :: Contact -> CallId -> CallExtraInfo -> RcvMessage -> CM () xCallExtra ct callId CallExtraInfo {rtcExtraInfo} msg = do msgCurrentCall ct callId "x.call.extra" msg $ \call -> case callState call of @@ -5290,13 +5305,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure (Just call, Nothing) -- to any call party - xCallEnd :: Contact -> CallId -> RcvMessage -> m () + xCallEnd :: Contact -> CallId -> RcvMessage -> CM () xCallEnd ct callId msg = msgCurrentCall ct callId "x.call.end" msg $ \Call {chatItemId} -> do toView $ CRCallEnded user ct (Nothing,) <$> callStatusItemContent user ct chatItemId WCSDisconnected - msgCurrentCall :: Contact -> CallId -> Text -> RcvMessage -> (Call -> m (Maybe Call, Maybe ACIContent)) -> m () + msgCurrentCall :: Contact -> CallId -> Text -> RcvMessage -> (Call -> CM (Maybe Call, Maybe ACIContent)) -> CM () msgCurrentCall ct@Contact {contactId = ctId'} callId' eventName RcvMessage {msgId} action = do calls <- asks currentCalls atomically (TM.lookup ctId' calls) >>= \case @@ -5315,11 +5330,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = forM_ aciContent_ $ \aciContent -> updateDirectChatItemView user ct chatItemId aciContent False $ Just msgId - msgCallStateError :: Text -> Call -> m () + msgCallStateError :: Text -> Call -> CM () msgCallStateError eventName Call {callState} = messageError $ eventName <> ": wrong call state " <> T.pack (show $ callStateTag callState) - mergeContacts :: Contact -> Contact -> m (Maybe Contact) + mergeContacts :: Contact -> Contact -> CM (Maybe Contact) mergeContacts c1 c2 = do let Contact {localDisplayName = cLDN1, profile = LocalProfile {displayName}} = c1 Contact {localDisplayName = cLDN2} = c2 @@ -5349,7 +5364,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> pure () _ -> pure () - associateMemberAndContact :: Contact -> GroupMember -> m (Maybe Contact) + associateMemberAndContact :: Contact -> GroupMember -> CM (Maybe Contact) associateMemberAndContact c m = do let Contact {localDisplayName = cLDN, profile = LocalProfile {displayName}} = c GroupMember {localDisplayName = mLDN} = m @@ -5367,21 +5382,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Just suffix -> readMaybe $ T.unpack suffix Nothing -> Nothing - associateMemberWithContact :: Contact -> GroupMember -> m Contact + associateMemberWithContact :: Contact -> GroupMember -> CM Contact associateMemberWithContact c1 m2@GroupMember {groupId} = do withStore' $ \db -> associateMemberWithContactRecord db user c1 m2 g <- withStore $ \db -> getGroupInfo db vr user groupId toView $ CRContactAndMemberAssociated user c1 g m2 c1 pure c1 - associateContactWithMember :: GroupMember -> Contact -> m Contact + associateContactWithMember :: GroupMember -> Contact -> CM Contact associateContactWithMember m1@GroupMember {groupId} c2 = do c2' <- withStore $ \db -> associateContactWithMemberRecord db vr user m1 c2 g <- withStore $ \db -> getGroupInfo db vr user groupId toView $ CRContactAndMemberAssociated user c2 g m1 c2' pure c2' - saveConnInfo :: Connection -> ConnInfo -> m Connection + saveConnInfo :: Connection -> ConnInfo -> CM Connection saveConnInfo activeConn connInfo = do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage activeConn connInfo conn' <- updatePeerChatVRange activeConn chatVRange @@ -5398,7 +5413,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO show/log error, other events in SMP confirmation _ -> pure conn' - xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> m () + xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> CM () xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do checkHostRole m memRole unless (sameMemberId memId $ membership gInfo) $ @@ -5418,7 +5433,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView gInfo ci toView $ CRJoinedGroupMemberConnecting user gInfo m announcedMember - xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> m () + xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> CM () xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do case memberCategory m of GCHostMember -> @@ -5442,14 +5457,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where createConn subMode = createAgentConnectionAsync user CFCreateConnGrpMemInv (chatHasNtfs chatSettings) SCMInvitation subMode - sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> m () + sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> CM () sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do hostConn <- withStore $ \db -> getConnectionById db vr user hostConnId let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq} void $ sendDirectMemberMessage hostConn msg groupId withStore' $ \db -> updateGroupMemberStatusById db userId groupMemberId GSMemIntroInvited - xGrpMemInv :: GroupInfo -> GroupMember -> MemberId -> IntroInvitation -> m () + xGrpMemInv :: GroupInfo -> GroupMember -> MemberId -> IntroInvitation -> CM () xGrpMemInv gInfo@GroupInfo {groupId} m memId introInv = do case memberCategory m of GCInviteeMember -> @@ -5462,7 +5477,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = \db -> updateIntroStatus db introId GMIntroInvForwarded _ -> messageError "x.grp.mem.inv can be only sent by invitee member" - xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> m () + xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> CM () xGrpMemFwd gInfo@GroupInfo {membership, chatSettings} m memInfo@(MemberInfo memId memRole memChatVRange _) introInv@IntroInvitation {groupConnReq, directConnReq} = do let GroupMember {memberId = membershipMemId} = membership checkHostRole m memRole @@ -5487,7 +5502,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = chatV = vr PQSupportOff `peerConnChatVersion` mcvr withStore' $ \db -> createIntroToMemberContact db user m toMember chatV mcvr groupConnIds directConnIds customUserProfileId subMode - xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> m () + xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> CM () xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg brokerTs | membershipMemId == memId = let gInfo' = gInfo {membership = membership {memberRole = memRole}} @@ -5506,11 +5521,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView gInfo ci toView CRMemberRole {user, groupInfo = gInfo', byMember = m, member = member {memberRole = memRole}, fromRole, toRole = memRole} - checkHostRole :: GroupMember -> GroupMemberRole -> m () + checkHostRole :: GroupMember -> GroupMemberRole -> CM () checkHostRole GroupMember {memberRole, localDisplayName} memRole = when (memberRole < GRAdmin || memberRole < memRole) $ throwChatError (CEGroupContactRole localDisplayName) - xGrpMemRestrict :: GroupInfo -> GroupMember -> MemberId -> MemberRestrictions -> RcvMessage -> UTCTime -> m () + xGrpMemRestrict :: GroupInfo -> GroupMember -> MemberId -> MemberRestrictions -> RcvMessage -> UTCTime -> CM () xGrpMemRestrict gInfo@GroupInfo {groupId, membership = GroupMember {memberId = membershipMemId}} m@GroupMember {memberRole = senderRole} @@ -5544,7 +5559,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = getGroupMember db vr user groupId bmId blocked = mrsBlocked restriction - xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> m () + xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> CM () xGrpMemCon gInfo sendingMember memId = do refMember <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo memId case (memberCategory sendingMember, memberCategory refMember) of @@ -5571,19 +5586,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> messageWarning "x.grp.mem.con: neither member is invitee" where - inviteeXGrpMemCon :: GroupMemberIntro -> m () + inviteeXGrpMemCon :: GroupMemberIntro -> CM () inviteeXGrpMemCon GroupMemberIntro {introId, introStatus} | introStatus == GMIntroReConnected = updateStatus introId GMIntroConnected | introStatus `elem` [GMIntroToConnected, GMIntroConnected] = pure () | otherwise = updateStatus introId GMIntroToConnected - forwardMemberXGrpMemCon :: GroupMemberIntro -> m () + forwardMemberXGrpMemCon :: GroupMemberIntro -> CM () forwardMemberXGrpMemCon GroupMemberIntro {introId, introStatus} | introStatus == GMIntroToConnected = updateStatus introId GMIntroConnected | introStatus `elem` [GMIntroReConnected, GMIntroConnected] = pure () | otherwise = updateStatus introId GMIntroReConnected updateStatus introId status = withStore' $ \db -> updateIntroStatus db introId status - xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> UTCTime -> m () + xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> UTCTime -> CM () xGrpMemDel gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId msg brokerTs = do let GroupMember {memberId = membershipMemId} = membership if membershipMemId == memId @@ -5615,7 +5630,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent) groupMsgToView gInfo ci - xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> m () + xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM () xGrpLeave gInfo m msg brokerTs = do deleteMemberConnection user m -- member record is not deleted to allow creation of "member left" chat item @@ -5624,7 +5639,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView gInfo ci toView $ CRLeftMember user gInfo m {memberStatus = GSMemLeft} - xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> m () + xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM () xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg brokerTs = do when (memberRole /= GROwner) $ throwChatError $ CEGroupUserRole gInfo GROwner ms <- withStore' $ \db -> do @@ -5637,7 +5652,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView gInfo ci toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m - xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> m () + xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM () xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg brokerTs | memberRole < GROwner = messageError "x.grp.info with insufficient member permissions" | otherwise = unless (p == p') $ do @@ -5649,7 +5664,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView g' ci createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' - xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> m () + xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM () xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do unless (groupFeatureAllowed SGFDirectMessages g) $ messageError "x.grp.direct.inv: direct messages not allowed" let GroupMember {memberContactId} = m @@ -5692,12 +5707,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc) toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat mCt') ci) - securityCodeChanged :: Contact -> m () + securityCodeChanged :: Contact -> CM () securityCodeChanged ct = do toView $ CRContactVerificationReset user ct createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent RCEVerificationCodeReset) Nothing - xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m () + xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> CM () xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId msg msgTs = do when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName) withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case @@ -5709,7 +5724,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Left e -> throwError $ ChatErrorStore e where -- Note: forwarded group events (see forwardedGroupMsg) should include msgId to be deduplicated - processForwardedMsg :: GroupMember -> ChatMessage 'Json -> m () + processForwardedMsg :: GroupMember -> ChatMessage 'Json -> CM () processForwardedMsg author chatMsg = do let body = LB.toStrict $ J.encode msg rcvMsg@RcvMessage {chatMsgEvent = ACME _ event} <- saveGroupFwdRcvMsg user groupId m author body chatMsg @@ -5729,12 +5744,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XGrpInfo p' -> xGrpInfo gInfo author p' rcvMsg msgTs _ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event) - createUnknownMember :: GroupInfo -> MemberId -> m GroupMember + createUnknownMember :: GroupInfo -> MemberId -> CM GroupMember createUnknownMember gInfo memberId = do let name = T.take 7 . safeDecodeUtf8 . B64.encode . unMemberId $ memberId withStore $ \db -> createNewUnknownGroupMember db vr user gInfo memberId name - directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () + directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM () directMsgReceived ct conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDDirectRcv ct) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do @@ -5746,14 +5761,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- - regular messages sent in batch would all be marked as delivered by a single receipt -- - repeat for directMsgReceived if same logic is applied to direct messages -- - getChatItemIdByAgentMsgId to return [ChatItemId] - groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () + groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM () groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus updateGroupItemStatus gInfo m conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete - updateDirectItemsStatus :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> m () + updateDirectItemsStatus :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () updateDirectItemsStatus ct conn msgIds newStatus = do cis_ <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemStatus' db ct conn msgId newStatus -- only send the last expired item event to view @@ -5761,7 +5776,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci : _ -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) _ -> pure () - updateDirectItemStatus :: Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> m () + updateDirectItemStatus :: Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> CM () updateDirectItemStatus ct conn msgId newStatus = do ci_ <- withStore $ \db -> updateDirectItemStatus' db ct conn msgId newStatus forM_ ci_ $ \ci -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) @@ -5775,7 +5790,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus _ -> pure Nothing - updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> m Bool + updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> CM Bool updateGroupMemSndStatus itemId groupMemberId newStatus = withStore' $ \db -> updateGroupMemSndStatus' db itemId groupMemberId newStatus @@ -5788,7 +5803,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> updateGroupSndStatus db itemId groupMemberId newStatus $> True _ -> pure False - updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> m () + updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> CM () updateGroupItemStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus = withStore' (\db -> getGroupChatItemByAgentMsgId db user groupId connId msgId) >>= \case Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure () @@ -5802,7 +5817,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem) _ -> pure () -createContactPQSndItem :: ChatMonad m => User -> Contact -> Connection -> PQEncryption -> m (Contact, Connection) +createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (Contact, Connection) createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = flip catchChatError (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of (Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled') @@ -5817,7 +5832,7 @@ createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = toView $ CRContactPQEnabled user ct' pqSndEnabled' pure (ct', conn') -updateContactPQRcv :: ChatMonad m => User -> Contact -> Connection -> PQEncryption -> m (Contact, Connection) +updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Contact, Connection) updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' = flip catchChatError (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of (Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled') @@ -5840,18 +5855,18 @@ sameMemberId :: MemberId -> GroupMember -> Bool sameMemberId memId GroupMember {memberId} = memId == memberId -- TODO v5.7 for contacts only version upgrade should trigger enabling PQ support/encryption -updatePeerChatVRange :: ChatMonad m => Connection -> VersionRangeChat -> m Connection +updatePeerChatVRange :: Connection -> VersionRangeChat -> CM Connection updatePeerChatVRange conn@Connection {connId, connChatVersion = v, peerChatVRange, pqSupport} msgVRange = do - v' <- upgradedConnVersion pqSupport v msgVRange + v' <- lift $ upgradedConnVersion pqSupport v msgVRange if msgVRange /= peerChatVRange || v' /= v then do withStore' $ \db -> setPeerChatVRange db connId v' msgVRange pure conn {connChatVersion = v', peerChatVRange = msgVRange} else pure conn -updateMemberChatVRange :: ChatMonad m => GroupMember -> Connection -> VersionRangeChat -> m (GroupMember, Connection) +updateMemberChatVRange :: GroupMember -> Connection -> VersionRangeChat -> CM (GroupMember, Connection) updateMemberChatVRange mem@GroupMember {groupMemberId} conn@Connection {connId, connChatVersion = v, peerChatVRange} msgVRange = do - v' <- upgradedConnVersion PQSupportOff v msgVRange + v' <- lift $ upgradedConnVersion PQSupportOff v msgVRange if msgVRange /= peerChatVRange || v' /= v then do withStore' $ \db -> do @@ -5861,31 +5876,31 @@ updateMemberChatVRange mem@GroupMember {groupMemberId} conn@Connection {connId, pure (mem {memberChatVRange = msgVRange, activeConn = Just conn'}, conn') else pure (mem, conn) -upgradedConnVersion :: ChatMonad' m => PQSupport -> VersionChat -> VersionRangeChat -> m VersionChat +upgradedConnVersion :: PQSupport -> VersionChat -> VersionRangeChat -> CM' VersionChat upgradedConnVersion pqSup v peerVR = do - vr <- chatVersionRange + vr <- chatVersionRange' -- don't allow reducing agreed connection version pure $ maybe v (\(Compatible v') -> max v v') $ vr pqSup `compatibleVersion` peerVR -parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p) +parseFileDescription :: FilePartyI p => Text -> CM (ValidFileDescription p) parseFileDescription = liftEither . first (ChatError . CEInvalidFileDescription) . (strDecode . encodeUtf8) -sendDirectFileInline :: ChatMonad m => User -> Contact -> FileTransferMeta -> SharedMsgId -> m () +sendDirectFileInline :: User -> Contact -> FileTransferMeta -> SharedMsgId -> CM () sendDirectFileInline user ct ft sharedMsgId = do msgDeliveryId <- sendFileInline_ ft sharedMsgId $ sendDirectContactMessage user ct withStore $ \db -> updateSndDirectFTDelivery db ct ft msgDeliveryId -sendMemberFileInline :: ChatMonad m => GroupMember -> Connection -> FileTransferMeta -> SharedMsgId -> m () +sendMemberFileInline :: GroupMember -> Connection -> FileTransferMeta -> SharedMsgId -> CM () sendMemberFileInline m@GroupMember {groupId} conn ft sharedMsgId = do msgDeliveryId <- sendFileInline_ ft sharedMsgId $ \msg -> do (sndMsg, msgDeliveryId, _) <- sendDirectMemberMessage conn msg groupId pure (sndMsg, msgDeliveryId) withStore' $ \db -> updateSndGroupFTDelivery db m conn ft msgDeliveryId -sendFileInline_ :: ChatMonad m => FileTransferMeta -> SharedMsgId -> (ChatMsgEvent 'Binary -> m (SndMessage, Int64)) -> m Int64 +sendFileInline_ :: FileTransferMeta -> SharedMsgId -> (ChatMsgEvent 'Binary -> CM (SndMessage, Int64)) -> CM Int64 sendFileInline_ FileTransferMeta {filePath, chunkSize} sharedMsgId sendMsg = - sendChunks 1 =<< liftIO . B.readFile =<< toFSFilePath filePath + sendChunks 1 =<< liftIO . B.readFile =<< lift (toFSFilePath filePath) where sendChunks chunkNo bytes = do let (chunk, rest) = B.splitAt chSize bytes @@ -5895,7 +5910,7 @@ sendFileInline_ FileTransferMeta {filePath, chunkSize} sharedMsgId sendMsg = else sendChunks (chunkNo + 1) rest chSize = fromIntegral chunkSize -parseChatMessage :: ChatMonad m => Connection -> ByteString -> m (ChatMessage 'Json) +parseChatMessage :: Connection -> ByteString -> CM (ChatMessage 'Json) parseChatMessage conn s = do case parseChatMessages s of [msg] -> liftEither . first (ChatError . errType) $ (\(ACMsg _ m) -> checkEncoding m) =<< msg @@ -5904,7 +5919,7 @@ parseChatMessage conn s = do errType = CEInvalidChatMessage conn Nothing (safeDecodeUtf8 s) {-# INLINE parseChatMessage #-} -sendFileChunk :: ChatMonad m => User -> SndFileTransfer -> m () +sendFileChunk :: User -> SndFileTransfer -> CM () sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentConnId acId} = unless (fileStatus == FSComplete || fileStatus == FSCancelled) $ do vr <- chatVersionRange @@ -5916,18 +5931,18 @@ sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentCo liftIO $ deleteSndFileChunks db ft updateDirectCIFileStatus db vr user fileId CIFSSndComplete toView $ CRSndFileComplete user ci ft - closeFileHandle fileId sndFiles + lift $ closeFileHandle fileId sndFiles deleteAgentConnectionAsync user acId -sendFileChunkNo :: ChatMonad m => SndFileTransfer -> Integer -> m () +sendFileChunkNo :: SndFileTransfer -> Integer -> CM () sendFileChunkNo ft@SndFileTransfer {agentConnId = AgentConnId acId} chunkNo = do chunkBytes <- readFileChunk ft chunkNo (msgId, _) <- withAgent $ \a -> sendMessage a acId PQEncOff SMP.noMsgFlags $ smpEncode FileChunk {chunkNo, chunkBytes} withStore' $ \db -> updateSndFileChunkMsg db ft chunkNo msgId -readFileChunk :: ChatMonad m => SndFileTransfer -> Integer -> m ByteString +readFileChunk :: SndFileTransfer -> Integer -> CM ByteString readFileChunk SndFileTransfer {fileId, filePath, chunkSize} chunkNo = do - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath read_ fsFilePath `catchThrow` (ChatError . CEFileRead filePath . show) where read_ fsFilePath = do @@ -5937,10 +5952,10 @@ readFileChunk SndFileTransfer {fileId, filePath, chunkSize} chunkNo = do when (pos /= pos') $ hSeek h AbsoluteSeek pos' liftIO . B.hGet h $ fromInteger chunkSize -parseFileChunk :: ChatMonad m => ByteString -> m FileChunk +parseFileChunk :: ByteString -> CM FileChunk parseFileChunk = liftEither . first (ChatError . CEFileRcvChunk) . smpDecode -appendFileChunk :: forall m. ChatMonad m => RcvFileTransfer -> Integer -> ByteString -> Bool -> m () +appendFileChunk :: RcvFileTransfer -> Integer -> ByteString -> Bool -> CM () appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitation = FileInvitation {fileName}} chunkNo chunk final = case fileStatus of RFSConnected RcvFileInfo {filePath} -> append_ filePath @@ -5950,16 +5965,16 @@ appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitati RFSCancelled _ -> pure () _ -> throwChatError $ CEFileInternal "receiving file transfer not in progress" where - append_ :: FilePath -> m () + append_ :: FilePath -> CM () append_ filePath = do - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath h <- getFileHandle fileId fsFilePath rcvFiles AppendMode liftIO (B.hPut h chunk >> hFlush h) `catchThrow` (fileErr . show) withStore' $ \db -> updatedRcvFileChunkStored db ft chunkNo when final $ do - closeFileHandle fileId rcvFiles + lift $ closeFileHandle fileId rcvFiles forM_ cryptoArgs $ \cfArgs -> do - tmpFile <- getChatTempDirectory >>= (`uniqueCombine` fileName) + tmpFile <- lift getChatTempDirectory >>= liftIO . (`uniqueCombine` fileName) tryChatError (liftError encryptErr $ encryptFile fsFilePath tmpFile cfArgs) >>= \case Right () -> do removeFile fsFilePath `catchChatError` \_ -> pure () @@ -5972,7 +5987,7 @@ appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitati encryptErr e = fileErr $ e <> ", received file not encrypted" fileErr = ChatError . CEFileWrite filePath -getFileHandle :: ChatMonad m => Int64 -> FilePath -> (ChatController -> TVar (Map Int64 Handle)) -> IOMode -> m Handle +getFileHandle :: Int64 -> FilePath -> (ChatController -> TVar (Map Int64 Handle)) -> IOMode -> CM Handle getFileHandle fileId filePath files ioMode = do fs <- asks files h_ <- M.lookup fileId <$> readTVarIO fs @@ -5983,17 +5998,17 @@ getFileHandle fileId filePath files ioMode = do atomically . modifyTVar fs $ M.insert fileId h pure h -isFileActive :: ChatMonad m => Int64 -> (ChatController -> TVar (Map Int64 Handle)) -> m Bool +isFileActive :: Int64 -> (ChatController -> TVar (Map Int64 Handle)) -> CM Bool isFileActive fileId files = do fs <- asks files isJust . M.lookup fileId <$> readTVarIO fs -cancelRcvFileTransfer :: ChatMonad m => User -> RcvFileTransfer -> m (Maybe ConnId) +cancelRcvFileTransfer :: User -> RcvFileTransfer -> CM (Maybe ConnId) cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInline} = cancel' `catchChatError` (\e -> toView (CRChatError (Just user) e) $> fileConnId) where cancel' = do - closeFileHandle fileId rcvFiles + lift $ closeFileHandle fileId rcvFiles withStore' $ \db -> do updateFileCancelled db user fileId CIFSRcvCancelled updateRcvFileStatus db fileId FSCancelled @@ -6005,7 +6020,7 @@ cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInlin pure fileConnId fileConnId = if isNothing xftpRcvFile && isNothing rcvFileInline then liveRcvFileTransferConnId ft else Nothing -cancelSndFile :: ChatMonad m => User -> FileTransferMeta -> [SndFileTransfer] -> Bool -> m [ConnId] +cancelSndFile :: User -> FileTransferMeta -> [SndFileTransfer] -> Bool -> CM [ConnId] cancelSndFile user FileTransferMeta {fileId, xftpSndFile} fts sendCancel = do withStore' (\db -> updateFileCancelled db user fileId CIFSSndCancelled) `catchChatError` (toView . CRChatError (Just user)) @@ -6014,11 +6029,11 @@ cancelSndFile user FileTransferMeta {fileId, xftpSndFile} fts sendCancel = do catMaybes <$> forM fts (\ft -> cancelSndFileTransfer user ft sendCancel) Just xsf -> do forM_ fts (\ft -> cancelSndFileTransfer user ft False) - agentXFTPDeleteSndFileRemote user xsf fileId `catchChatError` (toView . CRChatError (Just user)) + lift (agentXFTPDeleteSndFileRemote user xsf fileId) `catchChatError` (toView . CRChatError (Just user)) pure [] -- TODO v6.0 remove -cancelSndFileTransfer :: ChatMonad m => User -> SndFileTransfer -> Bool -> m (Maybe ConnId) +cancelSndFileTransfer :: User -> SndFileTransfer -> Bool -> CM (Maybe ConnId) cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, agentConnId = AgentConnId acId, fileStatus, fileInline} sendCancel = if fileStatus == FSCancelled || fileStatus == FSComplete then pure Nothing @@ -6037,40 +6052,40 @@ cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, age pure fileConnId fileConnId = if isNothing fileInline then Just acId else Nothing -closeFileHandle :: ChatMonad m => Int64 -> (ChatController -> TVar (Map Int64 Handle)) -> m () +closeFileHandle :: Int64 -> (ChatController -> TVar (Map Int64 Handle)) -> CM' () closeFileHandle fileId files = do fs <- asks files h_ <- atomically . stateTVar fs $ \m -> (M.lookup fileId m, M.delete fileId m) liftIO $ mapM_ hClose h_ `catchAll_` pure () -deleteMembersConnections :: ChatMonad m => User -> [GroupMember] -> m () +deleteMembersConnections :: User -> [GroupMember] -> CM () deleteMembersConnections user members = deleteMembersConnections' user members False -deleteMembersConnections' :: ChatMonad m => User -> [GroupMember] -> Bool -> m () +deleteMembersConnections' :: User -> [GroupMember] -> Bool -> CM () deleteMembersConnections' user members waitDelivery = do let memberConns = filter (\Connection {connStatus} -> connStatus /= ConnDeleted) $ mapMaybe (\GroupMember {activeConn} -> activeConn) members deleteAgentConnectionsAsync' user (map aConnId memberConns) waitDelivery - void . withStoreBatch' $ \db -> map (\conn -> updateConnectionStatus db conn ConnDeleted) memberConns + lift . void . withStoreBatch' $ \db -> map (\conn -> updateConnectionStatus db conn ConnDeleted) memberConns -deleteMemberConnection :: ChatMonad m => User -> GroupMember -> m () +deleteMemberConnection :: User -> GroupMember -> CM () deleteMemberConnection user mem = deleteMemberConnection' user mem False -deleteMemberConnection' :: ChatMonad m => User -> GroupMember -> Bool -> m () +deleteMemberConnection' :: User -> GroupMember -> Bool -> CM () deleteMemberConnection' user GroupMember {activeConn} waitDelivery = do forM_ activeConn $ \conn -> do deleteAgentConnectionAsync' user (aConnId conn) waitDelivery withStore' $ \db -> updateConnectionStatus db conn ConnDeleted -deleteOrUpdateMemberRecord :: ChatMonad m => User -> GroupMember -> m () +deleteOrUpdateMemberRecord :: User -> GroupMember -> CM () deleteOrUpdateMemberRecord user@User {userId} member = withStore' $ \db -> checkGroupMemberHasItems db user member >>= \case Just _ -> updateGroupMemberStatus db userId member GSMemRemoved Nothing -> deleteGroupMember db user member -sendDirectContactMessage :: (MsgEncodingI e, ChatMonad m) => User -> Contact -> ChatMsgEvent e -> m (SndMessage, Int64) +sendDirectContactMessage :: MsgEncodingI e => User -> Contact -> ChatMsgEvent e -> CM (SndMessage, Int64) sendDirectContactMessage user ct chatMsgEvent = do conn@Connection {connId, pqSupport} <- liftEither $ contactSendConn_ ct r <- sendDirectMessage_ conn pqSupport chatMsgEvent (ConnectionId connId) @@ -6091,10 +6106,10 @@ contactSendConn_ ct@Contact {activeConn} = case activeConn of -- unlike sendGroupMemberMessage, this function will not store message as pending -- TODO v5.8 we could remove pending messages once all clients support forwarding -sendDirectMemberMessage :: (MsgEncodingI e, ChatMonad m) => Connection -> ChatMsgEvent e -> GroupId -> m (SndMessage, Int64, PQEncryption) +sendDirectMemberMessage :: MsgEncodingI e => Connection -> ChatMsgEvent e -> GroupId -> CM (SndMessage, Int64, PQEncryption) sendDirectMemberMessage conn chatMsgEvent groupId = sendDirectMessage_ conn PQSupportOff chatMsgEvent (GroupId groupId) -sendDirectMessage_ :: (MsgEncodingI e, ChatMonad m) => Connection -> PQSupport -> ChatMsgEvent e -> ConnOrGroupId -> m (SndMessage, Int64, PQEncryption) +sendDirectMessage_ :: MsgEncodingI e => Connection -> PQSupport -> ChatMsgEvent e -> ConnOrGroupId -> CM (SndMessage, Int64, PQEncryption) sendDirectMessage_ conn pqSup chatMsgEvent connOrGroupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent connOrGroupId pqSup @@ -6102,14 +6117,14 @@ sendDirectMessage_ conn pqSup chatMsgEvent connOrGroupId = do (msgDeliveryId, pqEnc') <- deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId pure (msg, msgDeliveryId, pqEnc') -createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> PQSupport -> m SndMessage +createSndMessage :: MsgEncodingI e => ChatMsgEvent e -> ConnOrGroupId -> PQSupport -> CM SndMessage createSndMessage chatMsgEvent connOrGroupId pqSup = - liftEither . runIdentity =<< createSndMessages (Identity (connOrGroupId, pqSup, chatMsgEvent)) + liftEither . runIdentity =<< lift (createSndMessages $ Identity (connOrGroupId, pqSup, chatMsgEvent)) -createSndMessages :: forall e m t. (MsgEncodingI e, ChatMonad' m, Traversable t) => t (ConnOrGroupId, PQSupport, ChatMsgEvent e) -> m (t (Either ChatError SndMessage)) +createSndMessages :: forall e t. (MsgEncodingI e, Traversable t) => t (ConnOrGroupId, PQSupport, ChatMsgEvent e) -> CM' (t (Either ChatError SndMessage)) createSndMessages idsEvents = do g <- asks random - vr <- chatVersionRange + vr <- chatVersionRange' withStoreBatch $ \db -> fmap (createMsg db g vr) idsEvents where createMsg :: DB.Connection -> TVar ChaChaDRG -> (PQSupport -> VersionRangeChat) -> (ConnOrGroupId, PQSupport, ChatMsgEvent e) -> IO (Either ChatError SndMessage) @@ -6119,11 +6134,11 @@ createSndMessages idsEvents = do encodeMessage sharedMsgId = encodeChatMessage maxEncodedMsgLength ChatMessage {chatVRange = vr pqSup, msgId = Just sharedMsgId, chatMsgEvent = evnt} -sendGroupMemberMessages :: forall e m. (MsgEncodingI e, ChatMonad m) => User -> Connection -> NonEmpty (ChatMsgEvent e) -> GroupId -> m () +sendGroupMemberMessages :: forall e. MsgEncodingI e => User -> Connection -> NonEmpty (ChatMsgEvent e) -> GroupId -> CM () sendGroupMemberMessages user conn events groupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) let idsEvts = L.map (GroupId groupId,PQSupportOff,) events - (errs, msgs) <- partitionEithers . L.toList <$> createSndMessages idsEvts + (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts unless (null errs) $ toView $ CRChatErrors (Just user) errs forM_ (L.nonEmpty msgs) $ \msgs' -> do -- TODO v5.7 based on version (?) @@ -6136,11 +6151,11 @@ sendGroupMemberMessages user conn events groupId = do forM_ msgBatches $ \batch -> processSndMessageBatch conn batch `catchChatError` (toView . CRChatError (Just user)) -processSndMessageBatch :: ChatMonad m => Connection -> MsgBatch -> m () +processSndMessageBatch :: Connection -> MsgBatch -> CM () processSndMessageBatch conn@Connection {connId} (MsgBatch batchBody sndMsgs) = do (agentMsgId, _pqEnc) <- withAgent $ \a -> sendMessage a (aConnId conn) PQEncOff MsgFlags {notification = True} batchBody let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId} - void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs + lift . void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs -- TODO v5.7 update batching for groups batchSndMessagesJSON :: NonEmpty SndMessage -> [Either ChatError MsgBatch] @@ -6155,12 +6170,12 @@ batchSndMessagesJSON = batchMessages maxEncodedMsgLength . L.toList -- SMP.TBError tbe SndMessage {msgId} -> Left . ChatError $ CEInternalError (show tbe <> " " <> show msgId) -- SMP.TBTransmission {} -> Left . ChatError $ CEInternalError "batchTransmissions_ didn't produce a batch" -encodeConnInfo :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> m ByteString +encodeConnInfo :: MsgEncodingI e => ChatMsgEvent e -> CM ByteString encodeConnInfo chatMsgEvent = do vr <- chatVersionRange encodeConnInfoPQ PQSupportOff (maxVersion $ vr PQSupportOff) chatMsgEvent -encodeConnInfoPQ :: (MsgEncodingI e, ChatMonad m) => PQSupport -> VersionChat -> ChatMsgEvent e -> m ByteString +encodeConnInfoPQ :: MsgEncodingI e => PQSupport -> VersionChat -> ChatMsgEvent e -> CM ByteString encodeConnInfoPQ pqSup v chatMsgEvent = do vr <- chatVersionRange let info = ChatMessage {chatVRange = vr pqSup, msgId = Nothing, chatMsgEvent} @@ -6173,23 +6188,23 @@ encodeConnInfoPQ pqSup v chatMsgEvent = do _ -> pure connInfo ECMLarge -> throwChatError $ CEException "large info" -deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m (Int64, PQEncryption) +deliverMessage :: Connection -> CMEventTag e -> MsgBody -> MessageId -> CM (Int64, PQEncryption) deliverMessage conn cmEventTag msgBody msgId = do let msgFlags = MsgFlags {notification = hasNotification cmEventTag} deliverMessage' conn msgFlags msgBody msgId -deliverMessage' :: ChatMonad m => Connection -> MsgFlags -> MsgBody -> MessageId -> m (Int64, PQEncryption) +deliverMessage' :: Connection -> MsgFlags -> MsgBody -> MessageId -> CM (Int64, PQEncryption) deliverMessage' conn msgFlags msgBody msgId = - deliverMessages ((conn, msgFlags, msgBody, msgId) :| []) >>= \case + lift (deliverMessages ((conn, msgFlags, msgBody, msgId) :| [])) >>= \case r :| [] -> liftEither r rs -> throwChatError $ CEInternalError $ "deliverMessage: expected 1 result, got " <> show (length rs) type MsgReq = (Connection, MsgFlags, MsgBody, MessageId) -deliverMessages :: ChatMonad' m => NonEmpty MsgReq -> m (NonEmpty (Either ChatError (Int64, PQEncryption))) +deliverMessages :: NonEmpty MsgReq -> CM' (NonEmpty (Either ChatError (Int64, PQEncryption))) deliverMessages msgs = deliverMessagesB $ L.map Right msgs -deliverMessagesB :: forall m. ChatMonad' m => NonEmpty (Either ChatError MsgReq) -> m (NonEmpty (Either ChatError (Int64, PQEncryption))) +deliverMessagesB :: NonEmpty (Either ChatError MsgReq) -> CM' (NonEmpty (Either ChatError (Int64, PQEncryption))) deliverMessagesB msgReqs = do msgReqs' <- liftIO compressBodies sent <- L.zipWith prepareBatch msgReqs' <$> withAgent' (`sendMessagesB` L.map toAgent msgReqs') @@ -6226,7 +6241,7 @@ deliverMessagesB msgReqs = do where updatePQ = updateConnPQSndEnabled db connId pqSndEnabled' -sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) +sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, [GroupMember]) sendGroupMessage user gInfo members chatMsgEvent = do when shouldSendProfileUpdate $ sendProfileUpdate `catchChatError` (\e -> toView (CRChatError (Just user) e)) @@ -6248,7 +6263,7 @@ sendGroupMessage user gInfo members chatMsgEvent = do currentTs <- liftIO getCurrentTime withStore' $ \db -> updateUserMemberProfileSentAt db user gInfo currentTs -sendGroupMessage' :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) +sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, [GroupMember]) sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) PQSupportOff recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) @@ -6256,10 +6271,10 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do (toSend, pending) = foldr addMember ([], []) recipientMembers -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here msgReqs = map (\(_, conn) -> (conn, msgFlags, msgBody, msgId)) toSend - delivered <- maybe (pure []) (fmap L.toList . deliverMessages) $ L.nonEmpty msgReqs + delivered <- maybe (pure []) (fmap L.toList . lift . deliverMessages) $ L.nonEmpty msgReqs let errors = lefts delivered unless (null errors) $ toView $ CRChatErrors (Just user) errors - stored <- withStoreBatch' $ \db -> map (\m -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) pending + stored <- lift . withStoreBatch' $ \db -> map (\m -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) pending let sentToMembers = filterSent delivered toSend fst <> filterSent stored pending id pure (msg, sentToMembers) where @@ -6303,17 +6318,17 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c XGrpMsgForward {} -> True _ -> False -sendGroupMemberMessage :: forall e m. (MsgEncodingI e, ChatMonad m) => User -> GroupMember -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> m () -> m () +sendGroupMemberMessage :: MsgEncodingI e => User -> GroupMember -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> CM () -> CM () sendGroupMemberMessage user m@GroupMember {groupMemberId} chatMsgEvent groupId introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) PQSupportOff messageMember msg `catchChatError` (\e -> toView (CRChatError (Just user) e)) where - messageMember :: SndMessage -> m () + messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction chatMsgEvent [m] m) $ \case MSASend conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ -sendPendingGroupMessages :: ChatMonad m => User -> GroupMember -> Connection -> m () +sendPendingGroupMessages :: User -> GroupMember -> Connection -> CM () sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn = do pendingMessages <- withStore' $ \db -> getPendingGroupMessages db groupMemberId -- TODO ensure order - pending messages interleave with user input messages @@ -6330,25 +6345,25 @@ sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn _ -> pure () -- TODO [batch send] refactor direct message processing same as groups (e.g. checkIntegrity before processing) -saveDirectRcvMSG :: ChatMonad m => Connection -> MsgMeta -> CommandId -> MsgBody -> m (Connection, RcvMessage) -saveDirectRcvMSG conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody = +saveDirectRcvMSG :: Connection -> MsgMeta -> MsgBody -> CM (Connection, RcvMessage) +saveDirectRcvMSG conn@Connection {connId} agentMsgMeta msgBody = case parseChatMessages msgBody of [Right (ACMsg _ ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent})] -> do conn' <- updatePeerChatVRange conn chatVRange let agentMsgId = fst $ recipient agentMsgMeta newMsg = NewRcvMessage {chatMsgEvent, msgBody} - rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} + rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta} msg <- withStore $ \db -> createNewMessageAndRcvMsgDelivery db (ConnectionId connId) newMsg sharedMsgId_ rcvMsgDelivery Nothing pure (conn', msg) [Left e] -> error $ "saveDirectRcvMSG: error parsing chat message: " <> e _ -> error "saveDirectRcvMSG: batching not supported" -saveGroupRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> Connection -> MsgMeta -> CommandId -> MsgBody -> ChatMessage e -> m (GroupMember, Connection, RcvMessage) -saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} = do +saveGroupRcvMsg :: MsgEncodingI e => User -> GroupId -> GroupMember -> Connection -> MsgMeta -> MsgBody -> ChatMessage e -> CM (GroupMember, Connection, RcvMessage) +saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta msgBody ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} = do (am'@GroupMember {memberId = amMemId, groupMemberId = amGroupMemId}, conn') <- updateMemberChatVRange authorMember conn chatVRange let agentMsgId = fst $ recipient agentMsgMeta newMsg = NewRcvMessage {chatMsgEvent, msgBody} - rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} + rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta} msg <- withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery $ Just amGroupMemId) `catchChatError` \e -> case e of @@ -6361,7 +6376,7 @@ saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta _ -> throwError e pure (am', conn', msg) -saveGroupFwdRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> GroupMember -> MsgBody -> ChatMessage e -> m RcvMessage +saveGroupFwdRcvMsg :: MsgEncodingI e => User -> GroupId -> GroupMember -> GroupMember -> MsgBody -> ChatMessage e -> CM RcvMessage saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember@GroupMember {memberId = refMemberId} msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} = do let newMsg = NewRcvMessage {chatMsgEvent, msgBody} fwdMemberId = Just $ groupMemberId' forwardingMember @@ -6378,10 +6393,10 @@ saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember@GroupMember {me throwError e _ -> throwError e -saveSndChatItem :: (ChatMonad m, ChatTypeI c) => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd) +saveSndChatItem :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> CM (ChatItem c 'MDSnd) saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing False -saveSndChatItem' :: (ChatMonad m, ChatTypeI c) => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDSnd) +saveSndChatItem' :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CITimed -> Bool -> CM (ChatItem c 'MDSnd) saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem itemTimed live = do createdAt <- liftIO getCurrentTime ciId <- withStore' $ \db -> do @@ -6391,11 +6406,11 @@ saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem pure ciId pure $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemTimed live createdAt Nothing createdAt -saveRcvChatItem :: (ChatMonad m, ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv) +saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv) saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content = saveRcvChatItem' user cd msg sharedMsgId_ brokerTs content Nothing Nothing False -saveRcvChatItem' :: (ChatMonad m, ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> CIContent 'MDRcv -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDRcv) +saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> CIContent 'MDRcv -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> CM (ChatItem c 'MDRcv) saveRcvChatItem' user cd msg@RcvMessage {forwardedByMember} sharedMsgId_ brokerTs content ciFile itemTimed live = do createdAt <- liftIO getCurrentTime (ciId, quotedItem) <- withStore' $ \db -> do @@ -6405,20 +6420,20 @@ saveRcvChatItem' user cd msg@RcvMessage {forwardedByMember} sharedMsgId_ brokerT pure (ciId, quotedItem) pure $ mkChatItem cd ciId content ciFile quotedItem sharedMsgId_ itemTimed live brokerTs forwardedByMember createdAt -mkChatItem :: forall c d. (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> ChatItem c d +mkChatItem :: (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> ChatItem c d mkChatItem cd ciId content file quotedItem sharedMsgId itemTimed live itemTs forwardedByMember currentTs = let itemText = ciContentToText content itemStatus = ciCreateStatus content meta = mkCIMeta ciId content itemText itemStatus sharedMsgId Nothing False itemTimed (justTrue live) currentTs itemTs forwardedByMember currentTs currentTs in ChatItem {chatDir = toCIDirection cd, meta, content, formattedText = parseMaybeMarkdownList itemText, quotedItem, reactions = [], file} -deleteDirectCI :: (ChatMonad m, MsgDirectionI d) => User -> Contact -> ChatItem 'CTDirect d -> Bool -> Bool -> m ChatResponse +deleteDirectCI :: MsgDirectionI d => User -> Contact -> ChatItem 'CTDirect d -> Bool -> Bool -> CM ChatResponse deleteDirectCI user ct ci@ChatItem {file} byUser timed = do deleteCIFile user file withStore' $ \db -> deleteDirectChatItem db user ct ci pure $ CRChatItemDeleted user (AChatItem SCTDirect msgDirection (DirectChat ct) ci) Nothing byUser timed -deleteGroupCI :: (ChatMonad m, MsgDirectionI d) => User -> GroupInfo -> ChatItem 'CTGroup d -> Bool -> Bool -> Maybe GroupMember -> UTCTime -> m ChatResponse +deleteGroupCI :: MsgDirectionI d => User -> GroupInfo -> ChatItem 'CTGroup d -> Bool -> Bool -> Maybe GroupMember -> UTCTime -> CM ChatResponse deleteGroupCI user gInfo ci@ChatItem {file} byUser timed byGroupMember_ deletedTs = do deleteCIFile user file toCi <- withStore' $ \db -> @@ -6429,7 +6444,7 @@ deleteGroupCI user gInfo ci@ChatItem {file} byUser timed byGroupMember_ deletedT where gItem = AChatItem SCTGroup msgDirection (GroupChat gInfo) -deleteLocalCI :: (ChatMonad m, MsgDirectionI d) => User -> NoteFolder -> ChatItem 'CTLocal d -> Bool -> Bool -> m ChatResponse +deleteLocalCI :: MsgDirectionI d => User -> NoteFolder -> ChatItem 'CTLocal d -> Bool -> Bool -> CM ChatResponse deleteLocalCI user nf ci@ChatItem {file = file_} byUser timed = do forM_ file_ $ \file -> do let filesInfo = [mkCIFileInfo file] @@ -6437,14 +6452,14 @@ deleteLocalCI user nf ci@ChatItem {file = file_} byUser timed = do withStore' $ \db -> deleteLocalChatItem db user nf ci pure $ CRChatItemDeleted user (AChatItem SCTLocal msgDirection (LocalChat nf) ci) Nothing byUser timed -deleteCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () +deleteCIFile :: MsgDirectionI d => User -> Maybe (CIFile d) -> CM () deleteCIFile user file_ = forM_ file_ $ \file -> do let filesInfo = [mkCIFileInfo file] cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo -markDirectCIDeleted :: (ChatMonad m, MsgDirectionI d) => User -> Contact -> ChatItem 'CTDirect d -> MessageId -> Bool -> UTCTime -> m ChatResponse +markDirectCIDeleted :: MsgDirectionI d => User -> Contact -> ChatItem 'CTDirect d -> MessageId -> Bool -> UTCTime -> CM ChatResponse markDirectCIDeleted user ct ci@ChatItem {file} msgId byUser deletedTs = do cancelCIFile user file ci' <- withStore' $ \db -> markDirectChatItemDeleted db user ct ci msgId deletedTs @@ -6452,7 +6467,7 @@ markDirectCIDeleted user ct ci@ChatItem {file} msgId byUser deletedTs = do where ctItem = AChatItem SCTDirect msgDirection (DirectChat ct) -markGroupCIDeleted :: (ChatMonad m, MsgDirectionI d) => User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Bool -> Maybe GroupMember -> UTCTime -> m ChatResponse +markGroupCIDeleted :: MsgDirectionI d => User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Bool -> Maybe GroupMember -> UTCTime -> CM ChatResponse markGroupCIDeleted user gInfo ci@ChatItem {file} msgId byUser byGroupMember_ deletedTs = do cancelCIFile user file ci' <- withStore' $ \db -> markGroupChatItemDeleted db user gInfo ci msgId byGroupMember_ deletedTs @@ -6460,74 +6475,74 @@ markGroupCIDeleted user gInfo ci@ChatItem {file} msgId byUser byGroupMember_ del where gItem = AChatItem SCTGroup msgDirection (GroupChat gInfo) -cancelCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () +cancelCIFile :: MsgDirectionI d => User -> Maybe (CIFile d) -> CM () cancelCIFile user file_ = forM_ file_ $ \file -> do let filesInfo = [mkCIFileInfo file] cancelFilesInProgress user filesInfo -createAgentConnectionAsync :: forall m c. (ChatMonad m, ConnectionModeI c) => User -> CommandFunction -> Bool -> SConnectionMode c -> SubscriptionMode -> m (CommandId, ConnId) +createAgentConnectionAsync :: ConnectionModeI c => User -> CommandFunction -> Bool -> SConnectionMode c -> SubscriptionMode -> CM (CommandId, ConnId) createAgentConnectionAsync user cmdFunction enableNtfs cMode subMode = do cmdId <- withStore' $ \db -> createCommand db user Nothing cmdFunction connId <- withAgent $ \a -> createConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cMode IKPQOff subMode pure (cmdId, connId) -joinAgentConnectionAsync :: ChatMonad m => User -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> m (CommandId, ConnId) +joinAgentConnectionAsync :: User -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> CM (CommandId, ConnId) joinAgentConnectionAsync user enableNtfs cReqUri cInfo subMode = do cmdId <- withStore' $ \db -> createCommand db user Nothing CFJoinConn connId <- withAgent $ \a -> joinConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cReqUri cInfo PQSupportOff subMode pure (cmdId, connId) -allowAgentConnectionAsync :: (MsgEncodingI e, ChatMonad m) => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> m () +allowAgentConnectionAsync :: MsgEncodingI e => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> CM () allowAgentConnectionAsync user conn@Connection {connId, pqSupport, connChatVersion} confId msg = do cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFAllowConn dm <- encodeConnInfoPQ pqSupport connChatVersion msg withAgent $ \a -> allowConnectionAsync a (aCorrId cmdId) (aConnId conn) confId dm withStore' $ \db -> updateConnectionStatus db conn ConnAccepted -agentAcceptContactAsync :: (MsgEncodingI e, ChatMonad m) => User -> Bool -> InvitationId -> ChatMsgEvent e -> SubscriptionMode -> PQSupport -> VersionChat -> m (CommandId, ConnId) +agentAcceptContactAsync :: MsgEncodingI e => User -> Bool -> InvitationId -> ChatMsgEvent e -> SubscriptionMode -> PQSupport -> VersionChat -> CM (CommandId, ConnId) agentAcceptContactAsync user enableNtfs invId msg subMode pqSup chatV = do cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact dm <- encodeConnInfoPQ pqSup chatV msg connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm pqSup subMode pure (cmdId, connId) -deleteAgentConnectionAsync :: ChatMonad m => User -> ConnId -> m () +deleteAgentConnectionAsync :: User -> ConnId -> CM () deleteAgentConnectionAsync user acId = deleteAgentConnectionAsync' user acId False -deleteAgentConnectionAsync' :: ChatMonad m => User -> ConnId -> Bool -> m () +deleteAgentConnectionAsync' :: User -> ConnId -> Bool -> CM () deleteAgentConnectionAsync' user acId waitDelivery = do withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` (toView . CRChatError (Just user)) -deleteAgentConnectionsAsync :: ChatMonad m => User -> [ConnId] -> m () +deleteAgentConnectionsAsync :: User -> [ConnId] -> CM () deleteAgentConnectionsAsync user acIds = deleteAgentConnectionsAsync' user acIds False -deleteAgentConnectionsAsync' :: ChatMonad m => User -> [ConnId] -> Bool -> m () +deleteAgentConnectionsAsync' :: User -> [ConnId] -> Bool -> CM () deleteAgentConnectionsAsync' _ [] _ = pure () deleteAgentConnectionsAsync' user acIds waitDelivery = do withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` (toView . CRChatError (Just user)) -agentXFTPDeleteRcvFile :: ChatMonad m => RcvFileId -> FileTransferId -> m () +agentXFTPDeleteRcvFile :: RcvFileId -> FileTransferId -> CM () agentXFTPDeleteRcvFile aFileId fileId = do - withAgent (`xftpDeleteRcvFile` aFileId) + lift $ withAgent' (`xftpDeleteRcvFile` aFileId) withStore' $ \db -> setRcvFTAgentDeleted db fileId -agentXFTPDeleteRcvFiles :: ChatMonad m => [(XFTPRcvFile, FileTransferId)] -> m () +agentXFTPDeleteRcvFiles :: [(XFTPRcvFile, FileTransferId)] -> CM' () agentXFTPDeleteRcvFiles rcvFiles = do let rcvFiles' = filter (not . agentRcvFileDeleted . fst) rcvFiles rfIds = mapMaybe fileIds rcvFiles' - withAgent $ \a -> xftpDeleteRcvFiles a (map fst rfIds) + withAgent' $ \a -> xftpDeleteRcvFiles a (map fst rfIds) void . withStoreBatch' $ \db -> map (setRcvFTAgentDeleted db . snd) rfIds where fileIds :: (XFTPRcvFile, FileTransferId) -> Maybe (RcvFileId, FileTransferId) fileIds (XFTPRcvFile {agentRcvFileId = Just (AgentRcvFileId aFileId)}, fileId) = Just (aFileId, fileId) fileIds _ = Nothing -agentXFTPDeleteSndFileRemote :: ChatMonad m => User -> XFTPSndFile -> FileTransferId -> m () +agentXFTPDeleteSndFileRemote :: User -> XFTPSndFile -> FileTransferId -> CM' () agentXFTPDeleteSndFileRemote user xsf fileId = agentXFTPDeleteSndFilesRemote user [(xsf, fileId)] -agentXFTPDeleteSndFilesRemote :: forall m. ChatMonad m => User -> [(XFTPSndFile, FileTransferId)] -> m () +agentXFTPDeleteSndFilesRemote :: User -> [(XFTPSndFile, FileTransferId)] -> CM' () agentXFTPDeleteSndFilesRemote user sndFiles = do (_errs, redirects) <- partitionEithers <$> withStoreBatch' (\db -> map (lookupFileTransferRedirectMeta db user . snd) sndFiles) let redirects' = mapMaybe mapRedirectMeta $ concat redirects @@ -6535,18 +6550,18 @@ agentXFTPDeleteSndFilesRemote user sndFiles = do sndFilesAll' = filter (not . agentSndFileDeleted . fst) sndFilesAll sndFilesAll'' <- catMaybes <$> mapM sndFileDescr sndFilesAll' let sfs = map (\(XFTPSndFile {agentSndFileId = AgentSndFileId aFileId}, sfd, _) -> (aFileId, sfd)) sndFilesAll'' - withAgent $ \a -> xftpDeleteSndFilesRemote a (aUserId user) sfs + withAgent' $ \a -> xftpDeleteSndFilesRemote a (aUserId user) sfs void . withStoreBatch' $ \db -> map (setSndFTAgentDeleted db user . (\(_, _, fId) -> fId)) sndFilesAll'' where mapRedirectMeta :: FileTransferMeta -> Maybe (XFTPSndFile, FileTransferId) mapRedirectMeta FileTransferMeta {fileId = fileId, xftpSndFile = Just sndFileRedirect} = Just (sndFileRedirect, fileId) mapRedirectMeta _ = Nothing - sndFileDescr :: (XFTPSndFile, FileTransferId) -> m (Maybe (XFTPSndFile, ValidFileDescription 'FSender, FileTransferId)) + sndFileDescr :: (XFTPSndFile, FileTransferId) -> CM' (Maybe (XFTPSndFile, ValidFileDescription 'FSender, FileTransferId)) sndFileDescr (xsf@XFTPSndFile {privateSndFileDescr}, fileId) = join <$> forM privateSndFileDescr parseSndDescr where parseSndDescr sfdText = - tryChatError (parseFileDescription sfdText) >>= \case + tryChatError' (parseFileDescription sfdText) >>= \case Left _ -> pure Nothing Right sd -> pure $ Just (xsf, sd, fileId) @@ -6559,11 +6574,11 @@ userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do let userPrefs = maybe (preferences' user) (const Nothing) incognitoProfile in (p' :: Profile) {preferences = Just . toChatPrefs $ mergePreferences (userPreferences <$> ct) userPrefs} -createRcvFeatureItems :: forall m. ChatMonad m => User -> Contact -> Contact -> m () +createRcvFeatureItems :: User -> Contact -> Contact -> CM' () createRcvFeatureItems user ct ct' = createFeatureItems user ct ct' CDDirectRcv CIRcvChatFeature CIRcvChatPreference contactPreference -createSndFeatureItems :: forall m. ChatMonad m => User -> Contact -> Contact -> m () +createSndFeatureItems :: User -> Contact -> Contact -> CM' () createSndFeatureItems user ct ct' = createFeatureItems user ct ct' CDDirectSnd CISndChatFeature CISndChatPreference getPref where @@ -6571,7 +6586,7 @@ createSndFeatureItems user ct ct' = CUPContact {preference} -> preference CUPUser {preference} -> preference -createContactsSndFeatureItems :: forall m. ChatMonad m => User -> [ChangedProfileContact] -> m () +createContactsSndFeatureItems :: User -> [ChangedProfileContact] -> CM' () createContactsSndFeatureItems user cts = createContactsFeatureItems user cts' CDDirectSnd CISndChatFeature CISndChatPreference getPref where @@ -6583,8 +6598,7 @@ createContactsSndFeatureItems user cts = type FeatureContent a d = ChatFeature -> a -> Maybe Int -> CIContent d createFeatureItems :: - forall d m. - (MsgDirectionI d, ChatMonad m) => + MsgDirectionI d => User -> Contact -> Contact -> @@ -6592,24 +6606,24 @@ createFeatureItems :: FeatureContent PrefEnabled d -> FeatureContent FeatureAllowed d -> (forall f. ContactUserPreference (FeaturePreference f) -> FeaturePreference f) -> - m () + CM' () createFeatureItems user ct ct' = createContactsFeatureItems user [(ct, ct')] createContactsFeatureItems :: - forall d m. - (MsgDirectionI d, ChatMonad m) => + forall d. + MsgDirectionI d => User -> [(Contact, Contact)] -> (Contact -> ChatDirection 'CTDirect d) -> FeatureContent PrefEnabled d -> FeatureContent FeatureAllowed d -> (forall f. ContactUserPreference (FeaturePreference f) -> FeaturePreference f) -> - m () + CM' () createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do let dirsCIContents = map contactChangedFeatures cts (errs, acis) <- partitionEithers <$> createInternalItemsForChats user Nothing dirsCIContents - unless (null errs) $ toView $ CRChatErrors (Just user) errs - forM_ acis $ \aci -> toView $ CRNewChatItem user aci + unless (null errs) $ toView' $ CRChatErrors (Just user) errs + forM_ acis $ \aci -> toView' $ CRNewChatItem user aci where contactChangedFeatures :: (Contact, Contact) -> (ChatDirection 'CTDirect d, [CIContent d]) contactChangedFeatures (Contact {mergedPreferences = cups}, ct'@Contact {mergedPreferences = cups'}) = do @@ -6632,7 +6646,7 @@ createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do cup = getContactUserPreference f cups cup' = getContactUserPreference f cups' -createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> CIContent d) -> GroupInfo -> GroupInfo -> m () +createGroupFeatureChangedItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> CIContent d) -> GroupInfo -> GroupInfo -> CM () createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences = gps} GroupInfo {fullGroupPreferences = gps'} = forM_ allGroupFeatures $ \(AGF f) -> do let state = groupFeatureState $ getGroupPreference f gps @@ -6644,20 +6658,20 @@ createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences sameGroupProfileInfo :: GroupProfile -> GroupProfile -> Bool sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferences = Nothing} -createInternalChatItem :: (ChatTypeI c, MsgDirectionI d, ChatMonad m) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> m () +createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM () createInternalChatItem user cd content itemTs_ = - createInternalItemsForChats user itemTs_ [(cd, [content])] >>= \case + lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case [Right aci] -> toView $ CRNewChatItem user aci [Left e] -> throwError e rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs) createInternalItemsForChats :: - forall c d m. - (ChatTypeI c, MsgDirectionI d, ChatMonad' m) => + forall c d. + (ChatTypeI c, MsgDirectionI d) => User -> Maybe UTCTime -> [(ChatDirection c d, [CIContent d])] -> - m [Either ChatError AChatItem] + CM' [Either ChatError AChatItem] createInternalItemsForChats user itemTs_ dirsCIContents = do createdAt <- liftIO getCurrentTime let itemTs = fromMaybe createdAt itemTs_ @@ -6674,7 +6688,7 @@ createInternalItemsForChats user itemTs_ dirsCIContents = do let ci = mkChatItem cd ciId content Nothing Nothing Nothing Nothing False itemTs Nothing createdAt pure $ AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci -createLocalChatItem :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTLocal d -> CIContent d -> UTCTime -> m ChatItemId +createLocalChatItem :: MsgDirectionI d => User -> ChatDirection 'CTLocal d -> CIContent d -> UTCTime -> CM ChatItemId createLocalChatItem user cd content createdAt = do gVar <- asks random withStore $ \db -> do @@ -6683,7 +6697,7 @@ createLocalChatItem user cd content createdAt = do let smi_ = Just (SharedMsgId sharedMsgId) in createNewChatItem_ db user cd Nothing smi_ content (Nothing, Nothing, Nothing, Nothing, Nothing) Nothing False createdAt Nothing createdAt -withUser' :: ChatMonad m => (User -> m ChatResponse) -> m ChatResponse +withUser' :: (User -> CM ChatResponse) -> CM ChatResponse withUser' action = asks currentUser >>= readTVarIO @@ -6691,30 +6705,30 @@ withUser' action = where run u = action u `catchChatError` (pure . CRChatCmdError (Just u)) -withUser :: ChatMonad m => (User -> m ChatResponse) -> m ChatResponse +withUser :: (User -> CM ChatResponse) -> CM ChatResponse withUser action = withUser' $ \user -> - ifM chatStarted (action user) (throwChatError CEChatNotStarted) + ifM (lift chatStarted) (action user) (throwChatError CEChatNotStarted) -withUser_ :: ChatMonad m => m ChatResponse -> m ChatResponse +withUser_ :: CM ChatResponse -> CM ChatResponse withUser_ = withUser . const -withUserId' :: ChatMonad m => UserId -> (User -> m ChatResponse) -> m ChatResponse +withUserId' :: UserId -> (User -> CM ChatResponse) -> CM ChatResponse withUserId' userId action = withUser' $ \user -> do checkSameUser userId user action user -withUserId :: ChatMonad m => UserId -> (User -> m ChatResponse) -> m ChatResponse +withUserId :: UserId -> (User -> CM ChatResponse) -> CM ChatResponse withUserId userId action = withUser $ \user -> do checkSameUser userId user action user -checkSameUser :: ChatMonad m => UserId -> User -> m () +checkSameUser :: UserId -> User -> CM () checkSameUser userId User {userId = activeUserId} = when (userId /= activeUserId) $ throwChatError (CEDifferentActiveUser userId activeUserId) -chatStarted :: ChatMonad m => m Bool +chatStarted :: CM' Bool chatStarted = fmap isJust . readTVarIO =<< asks agentAsync -waitChatStartedAndActivated :: ChatMonad m => m () +waitChatStartedAndActivated :: CM' () waitChatStartedAndActivated = do agentStarted <- asks agentAsync chatActivated <- asks chatActivated @@ -6723,11 +6737,15 @@ waitChatStartedAndActivated = do activated <- readTVar chatActivated unless (isJust started && activated) retry -chatVersionRange :: ChatMonad' m => m (PQSupport -> VersionRangeChat) -chatVersionRange = do +chatVersionRange :: CM (PQSupport -> VersionRangeChat) +chatVersionRange = lift chatVersionRange' +{-# INLINE chatVersionRange #-} + +chatVersionRange' :: CM' (PQSupport -> VersionRangeChat) +chatVersionRange' = do ChatConfig {chatVRange} <- asks config pure chatVRange -{-# INLINE chatVersionRange #-} +{-# INLINE chatVersionRange' #-} chatCommandP :: Parser ChatCommand chatCommandP = @@ -7034,7 +7052,8 @@ chatCommandP = "/get subs" $> GetAgentSubs, "/get subs details" $> GetAgentSubsDetails, "/get workers" $> GetAgentWorkers, - "/get workers details" $> GetAgentWorkersDetails + "/get workers details" $> GetAgentWorkersDetails, + "//" *> (CustomChatCommand <$> A.takeByteString) ] where choice = A.choice . map (\p -> p <* A.takeWhile (== ' ') <* A.endOfInput) @@ -7178,13 +7197,13 @@ simplexContactProfile = preferences = Nothing } -timeItToView :: ChatMonad' m => String -> m a -> m a +timeItToView :: String -> CM' a -> CM' a timeItToView s action = do t1 <- liftIO getCurrentTime a <- action t2 <- liftIO getCurrentTime let diff = diffToMilliseconds $ diffUTCTime t2 t1 - toView $ CRTimedAction s diff + toView' $ CRTimedAction s diff pure a mkValidName :: String -> String @@ -7206,11 +7225,11 @@ mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 | otherwise = validFirstChar || isSpace c || isMark c || isPunctuation c validFirstChar = isLetter c || isNumber c || isSymbol c -xftpSndFileTransfer_ :: ChatMonad m => User -> CryptoFile -> Integer -> Int -> Maybe ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) +xftpSndFileTransfer_ :: User -> CryptoFile -> Integer -> Int -> Maybe ContactOrGroup -> CM (FileInvitation, CIFile 'MDSnd, FileTransferMeta) xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup_ = do let fileName = takeFileName filePath fInv = xftpFileInvitation fileName fileSize dummyFileDescr - fsFilePath <- toFSFilePath filePath + fsFilePath <- lift $ toFSFilePath filePath let srcFile = CryptoFile fsFilePath cfArgs aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) -- TODO CRSndFileStart event for XFTP @@ -7220,7 +7239,7 @@ xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOr ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} pure (fInv, ciFile, ft) -xftpSndFileRedirect :: ChatMonad m => User -> FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect :: User -> FileTransferId -> ValidFileDescription 'FRecipient -> CM FileTransferMeta xftpSndFileRedirect user ftId vfd = do let fileName = "redirect.yaml" file = CryptoFile fileName Nothing diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index 4644299598..d51f60f5f1 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -44,7 +44,7 @@ archiveChatDbFile = "simplex_v1_chat.db" archiveFilesFolder :: String archiveFilesFolder = "simplex_v1_files" -exportArchive :: ChatMonad m => ArchiveConfig -> m () +exportArchive :: ArchiveConfig -> CM' () exportArchive cfg@ArchiveConfig {archivePath, disableCompression} = withTempDir cfg "simplex-chat." $ \dir -> do StorageFiles {chatStore, agentStore, filesPath} <- storageFiles @@ -55,7 +55,7 @@ exportArchive cfg@ArchiveConfig {archivePath, disableCompression} = let method = if disableCompression == Just True then Z.Store else Z.Deflate Z.createArchive archivePath $ Z.packDirRecur method Z.mkEntrySelector dir -importArchive :: ChatMonad m => ArchiveConfig -> m [ArchiveError] +importArchive :: ArchiveConfig -> CM' [ArchiveError] importArchive cfg@ArchiveConfig {archivePath} = withTempDir cfg "simplex-chat." $ \dir -> do Z.withArchive archivePath $ Z.unpackInto dir @@ -78,12 +78,12 @@ importArchive cfg@ArchiveConfig {archivePath} = (pure []) _ -> pure [] -withTempDir :: ChatMonad m => ArchiveConfig -> (String -> (FilePath -> m a) -> m a) +withTempDir :: ArchiveConfig -> (String -> (FilePath -> CM' a) -> CM' a) withTempDir cfg = case parentTempDirectory (cfg :: ArchiveConfig) of Just tmpDir -> withTempDirectory tmpDir _ -> withSystemTempDirectory -copyDirectoryFiles :: ChatMonad m => FilePath -> FilePath -> m [ArchiveError] +copyDirectoryFiles :: FilePath -> FilePath -> CM' [ArchiveError] copyDirectoryFiles fromDir toDir = do createDirectoryIfMissing False toDir fs <- listDirectory fromDir @@ -97,9 +97,9 @@ copyDirectoryFiles fromDir toDir = do f' = fromDir fn whenM (doesFileExist f') $ copyFile f' $ toDir fn -deleteStorage :: ChatMonad m => m () +deleteStorage :: CM () deleteStorage = do - fs <- storageFiles + fs <- lift storageFiles liftIO $ closeSQLiteStore `withStores` fs remove `withDBs` fs mapM_ removeDir $ filesPath fs @@ -114,17 +114,17 @@ data StorageFiles = StorageFiles filesPath :: Maybe FilePath } -storageFiles :: ChatMonad m => m StorageFiles +storageFiles :: CM' StorageFiles storageFiles = do ChatController {chatStore, filesFolder, smpAgent} <- ask let agentStore = agentClientStore smpAgent filesPath <- readTVarIO filesFolder pure StorageFiles {chatStore, agentStore, filesPath} -sqlCipherExport :: forall m. ChatMonad m => DBEncryptionConfig -> m () +sqlCipherExport :: DBEncryptionConfig -> CM () sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = DBEncryptionKey key', keepKey} = when (key /= key') $ do - fs <- storageFiles + fs <- lift storageFiles checkFile `withDBs` fs backup `withDBs` fs checkEncryption `withStores` fs @@ -159,7 +159,7 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D "DETACH DATABASE exported;" ] -withDB :: forall a m. ChatMonad m => FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> m () +withDB :: FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> CM () withDB f' a err = liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing) `catch` checkSQLError @@ -169,7 +169,7 @@ withDB f' a err = checkSQLError e = case SQL.sqlError e of SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase _ -> sqliteError' e - sqliteError' :: Show e => e -> m (Maybe SQLiteError) + sqliteError' :: Show e => e -> CM (Maybe SQLiteError) sqliteError' = pure . Just . SQLiteError . show testSQL :: BA.ScrubbedBytes -> Text @@ -184,9 +184,9 @@ testSQL k = keySQL :: BA.ScrubbedBytes -> [Text] keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)] -sqlCipherTestKey :: forall m. ChatMonad m => DBEncryptionKey -> m () +sqlCipherTestKey :: DBEncryptionKey -> CM () sqlCipherTestKey (DBEncryptionKey key) = do - fs <- storageFiles + fs <- lift storageFiles testKey `withDBs` fs where testKey f = withDB f (`SQL.exec` testSQL key) DBErrorOpen diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 4ca9da094f..0291793843 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -62,6 +62,7 @@ import Simplex.Chat.Remote.Types import Simplex.Chat.Store (AutoAccept, StoreError (..), UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types import Simplex.Chat.Types.Preferences +import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure) @@ -82,7 +83,7 @@ import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), Cor import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) import Simplex.Messaging.Transport.Client (TransportHost) -import Simplex.Messaging.Util (allFinally, catchAllErrors, liftIOEither, tryAllErrors, (<$$>)) +import Simplex.Messaging.Util (allFinally, catchAllErrors, catchAllErrors', tryAllErrors, tryAllErrors', (<$$>)) import Simplex.RemoteControl.Client import Simplex.RemoteControl.Invitation (RCSignedInvitation, RCVerifiedInvitation) import Simplex.RemoteControl.Types @@ -144,9 +145,28 @@ data ChatConfig = ChatConfig ciExpirationInterval :: Int64, -- microseconds coreApi :: Bool, highlyAvailable :: Bool, - deviceNameForRemote :: Text + deviceNameForRemote :: Text, + chatHooks :: ChatHooks } +-- The hooks can be used to extend or customize chat core in mobile or CLI clients. +data ChatHooks = ChatHooks + { -- preCmdHook can be used to process or modify the commands before they are processed. + -- This hook should be used to process CustomChatCommand. + -- if this hook returns ChatResponse, the command processing will be skipped. + preCmdHook :: ChatController -> ChatCommand -> IO (Either ChatResponse ChatCommand), + -- eventHook can be used to additionally process or modify events, + -- it is called before the event is sent to the user (or to the UI). + eventHook :: ChatController -> ChatResponse -> IO ChatResponse + } + +defaultChatHooks :: ChatHooks +defaultChatHooks = + ChatHooks + { preCmdHook = \_ -> pure . Right, + eventHook = \_ -> pure + } + data DefaultAgentServers = DefaultAgentServers { smp :: NonEmpty SMPServerWithAuth, ntf :: [NtfServer], @@ -471,6 +491,9 @@ data ChatCommand | GetAgentSubsDetails | GetAgentWorkers | GetAgentWorkersDetails + -- The parser will return this command for strings that start from "//". + -- This command should be processed in preCmdHook + | CustomChatCommand ByteString deriving (Show) allowRemoteCommand :: ChatCommand -> Bool -- XXX: consider using Relay/Block/ForceLocal @@ -597,10 +620,9 @@ data ChatResponse | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} | CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo} - | CRRcvFileDescrReady {user :: User, chatItem :: AChatItem} + | CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr} | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} | CRStandaloneFileInfo {fileMeta :: Maybe J.Value} | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download | CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats @@ -726,6 +748,7 @@ data ChatResponse | CRArchiveImported {archiveErrors :: [ArchiveError]} | CRAppSettings {appSettings :: AppSettings} | CRTimedAction {action :: String, durationMilliseconds :: Int64} + | CRCustomChatResponse {user_ :: Maybe User, response :: Text} deriving (Show) -- some of these can only be used as command responses @@ -1118,7 +1141,7 @@ data DatabaseError data SQLiteError = SQLiteErrorNotADatabase | SQLiteError String deriving (Show, Exception) -throwDBError :: ChatMonad m => DatabaseError -> m () +throwDBError :: DatabaseError -> CM () throwDBError = throwError . ChatErrorDatabase -- TODO review errors, some of it can be covered by HTTP2 errors @@ -1222,39 +1245,59 @@ data RemoteCtrlInfo = RemoteCtrlInfo } deriving (Show) -type ChatMonad' m = (MonadUnliftIO m, MonadReader ChatController m) +type CM' a = ReaderT ChatController IO a -type ChatMonad m = (ChatMonad' m, MonadError ChatError m) +type CM a = ExceptT ChatError (ReaderT ChatController IO) a -chatReadVar :: ChatMonad' m => (ChatController -> TVar a) -> m a -chatReadVar f = asks f >>= readTVarIO +chatReadVar :: (ChatController -> TVar a) -> CM a +chatReadVar = lift . chatReadVar' {-# INLINE chatReadVar #-} -chatWriteVar :: ChatMonad' m => (ChatController -> TVar a) -> a -> m () -chatWriteVar f value = asks f >>= atomically . (`writeTVar` value) +chatReadVar' :: (ChatController -> TVar a) -> CM' a +chatReadVar' f = asks f >>= readTVarIO +{-# INLINE chatReadVar' #-} + +chatWriteVar :: (ChatController -> TVar a) -> a -> CM () +chatWriteVar f = lift . chatWriteVar' f {-# INLINE chatWriteVar #-} -chatModifyVar :: ChatMonad' m => (ChatController -> TVar a) -> (a -> a) -> m () -chatModifyVar f newValue = asks f >>= atomically . (`modifyTVar'` newValue) +chatWriteVar' :: (ChatController -> TVar a) -> a -> CM' () +chatWriteVar' f value = asks f >>= atomically . (`writeTVar` value) +{-# INLINE chatWriteVar' #-} + +chatModifyVar :: (ChatController -> TVar a) -> (a -> a) -> CM () +chatModifyVar f = lift . chatModifyVar' f {-# INLINE chatModifyVar #-} -setContactNetworkStatus :: ChatMonad' m => Contact -> NetworkStatus -> m () -setContactNetworkStatus Contact {activeConn = Nothing} _ = pure () -setContactNetworkStatus Contact {activeConn = Just Connection {agentConnId}} status = chatModifyVar connNetworkStatuses $ M.insert agentConnId status +chatModifyVar' :: (ChatController -> TVar a) -> (a -> a) -> CM' () +chatModifyVar' f newValue = asks f >>= atomically . (`modifyTVar'` newValue) +{-# INLINE chatModifyVar' #-} -tryChatError :: ChatMonad m => m a -> m (Either ChatError a) +setContactNetworkStatus :: Contact -> NetworkStatus -> CM' () +setContactNetworkStatus Contact {activeConn = Nothing} _ = pure () +setContactNetworkStatus Contact {activeConn = Just Connection {agentConnId}} status = chatModifyVar' connNetworkStatuses $ M.insert agentConnId status + +tryChatError :: CM a -> CM (Either ChatError a) tryChatError = tryAllErrors mkChatError {-# INLINE tryChatError #-} -catchChatError :: ChatMonad m => m a -> (ChatError -> m a) -> m a +tryChatError' :: CM a -> CM' (Either ChatError a) +tryChatError' = tryAllErrors' mkChatError +{-# INLINE tryChatError' #-} + +catchChatError :: CM a -> (ChatError -> CM a) -> CM a catchChatError = catchAllErrors mkChatError {-# INLINE catchChatError #-} -chatFinally :: ChatMonad m => m a -> m b -> m a +catchChatError' :: CM a -> (ChatError -> CM' a) -> CM' a +catchChatError' = catchAllErrors' mkChatError +{-# INLINE catchChatError' #-} + +chatFinally :: CM a -> CM b -> CM a chatFinally = allFinally mkChatError {-# INLINE chatFinally #-} -onChatError :: ChatMonad m => m a -> m b -> m a +onChatError :: CM a -> CM b -> CM a a `onChatError` onErr = a `catchChatError` \e -> onErr >> throwError e {-# INLINE onChatError #-} @@ -1273,14 +1316,18 @@ mkStoreError = SEInternalError . show chatCmdError :: Maybe User -> String -> ChatResponse chatCmdError user = CRChatCmdError user . ChatError . CECommandError -throwChatError :: ChatMonad m => ChatErrorType -> m a +throwChatError :: ChatErrorType -> CM a throwChatError = throwError . ChatError -- | Emit local events. -toView :: ChatMonad' m => ChatResponse -> m () -toView event = do - localQ <- asks outputQ - session <- asks remoteCtrlSession +toView :: ChatResponse -> CM () +toView = lift . toView' +{-# INLINE toView #-} + +toView' :: ChatResponse -> CM' () +toView' ev = do + cc@ChatController {outputQ = localQ, remoteCtrlSession = session, config = ChatConfig {chatHooks}} <- ask + event <- liftIO $ eventHook chatHooks cc ev atomically $ readTVar session >>= \case Just (_, RCSessionConnected {remoteOutputQ}) @@ -1288,15 +1335,15 @@ toView event = do -- TODO potentially, it should hold some events while connecting _ -> writeTBQueue localQ (Nothing, Nothing, event) -withStore' :: ChatMonad m => (DB.Connection -> IO a) -> m a +withStore' :: (DB.Connection -> IO a) -> CM a withStore' action = withStore $ liftIO . action -withStore :: ChatMonad m => (DB.Connection -> ExceptT StoreError IO a) -> m a +withStore :: (DB.Connection -> ExceptT StoreError IO a) -> CM a withStore action = do ChatController {chatStore} <- ask liftIOEither $ withTransaction chatStore (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors -withStoreBatch :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO (Either ChatError a))) -> m (t (Either ChatError a)) +withStoreBatch :: Traversable t => (DB.Connection -> t (IO (Either ChatError a))) -> CM' (t (Either ChatError a)) withStoreBatch actions = do ChatController {chatStore} <- ask liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions @@ -1310,17 +1357,17 @@ handleDBErrors = E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e ] -withStoreBatch' :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO a)) -> m (t (Either ChatError a)) +withStoreBatch' :: Traversable t => (DB.Connection -> t (IO a)) -> CM' (t (Either ChatError a)) withStoreBatch' actions = withStoreBatch $ fmap (fmap Right) . actions -withAgent :: ChatMonad m => (AgentClient -> ExceptT AgentErrorType m a) -> m a +withAgent :: (AgentClient -> ExceptT AgentErrorType IO a) -> CM a withAgent action = asks smpAgent - >>= runExceptT . action + >>= liftIO . runExceptT . action >>= liftEither . first (`ChatErrorAgent` Nothing) -withAgent' :: ChatMonad' m => (AgentClient -> m a) -> m a -withAgent' action = asks smpAgent >>= action +withAgent' :: (AgentClient -> IO a) -> CM' a +withAgent' action = asks smpAgent >>= liftIO . action $(JQ.deriveJSON (enumJSON $ dropPrefix "HS") ''HelpSection) diff --git a/src/Simplex/Chat/Files.hs b/src/Simplex/Chat/Files.hs index 9c6d731dd7..0c04b22e28 100644 --- a/src/Simplex/Chat/Files.hs +++ b/src/Simplex/Chat/Files.hs @@ -3,13 +3,12 @@ module Simplex.Chat.Files where -import Control.Monad.IO.Class import Simplex.Chat.Controller import Simplex.Messaging.Util (ifM) import System.FilePath (combine, splitExtensions) import UnliftIO.Directory (doesDirectoryExist, doesFileExist, getHomeDirectory, getTemporaryDirectory) -uniqueCombine :: MonadIO m => FilePath -> String -> m FilePath +uniqueCombine :: FilePath -> String -> IO FilePath uniqueCombine fPath fName = tryCombine (0 :: Int) where tryCombine n = @@ -18,10 +17,10 @@ uniqueCombine fPath fName = tryCombine (0 :: Int) f = fPath `combine` (name <> suffix <> ext) in ifM (doesFileExist f) (tryCombine $ n + 1) (pure f) -getChatTempDirectory :: ChatMonad m => m FilePath -getChatTempDirectory = chatReadVar tempDirectory >>= maybe getTemporaryDirectory pure +getChatTempDirectory :: CM' FilePath +getChatTempDirectory = chatReadVar' tempDirectory >>= maybe getTemporaryDirectory pure -getDefaultFilesFolder :: ChatMonad m => m FilePath +getDefaultFilesFolder :: CM' FilePath getDefaultFilesFolder = do dir <- (`combine` "Downloads") <$> getHomeDirectory ifM (doesDirectoryExist dir) (pure dir) getChatTempDirectory diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 4312cfa858..c24de3b2e1 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -24,7 +24,6 @@ import qualified Data.Aeson as J import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ import qualified Data.Attoparsec.ByteString.Char8 as A -import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Lazy.Char8 as LB import Data.Char (isSpace) import Data.Int (Int64) @@ -48,6 +47,7 @@ import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF +import qualified Simplex.Messaging.Encoding.Base64 as B64 import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) import Simplex.Messaging.Protocol (MsgBody) @@ -871,8 +871,7 @@ data SndMsgDelivery = SndMsgDelivery data RcvMsgDelivery = RcvMsgDelivery { connId :: Int64, agentMsgId :: AgentMsgId, - agentMsgMeta :: MsgMeta, - agentAckCmdId :: CommandId + agentMsgMeta :: MsgMeta } deriving (Show) @@ -899,7 +898,7 @@ msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId data MsgDeliveryStatus (d :: MsgDirection) where MDSRcvAgent :: MsgDeliveryStatus 'MDRcv - MDSRcvAcknowledged :: MsgDeliveryStatus 'MDRcv + MDSRcvAcknowledged :: MsgDeliveryStatus 'MDRcv -- not used MDSSndPending :: MsgDeliveryStatus 'MDSnd MDSSndAgent :: MsgDeliveryStatus 'MDSnd MDSSndSent :: MsgDeliveryStatus 'MDSnd diff --git a/src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs b/src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs new file mode 100644 index 0000000000..c14f08447e --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240313_drop_agent_ack_cmd_id :: Query +m20240313_drop_agent_ack_cmd_id = + [sql| +DROP INDEX idx_msg_deliveries_agent_ack_cmd_id; + +ALTER TABLE msg_deliveries DROP COLUMN agent_ack_cmd_id; +|] + +down_m20240313_drop_agent_ack_cmd_id :: Query +down_m20240313_drop_agent_ack_cmd_id = + [sql| +ALTER TABLE msg_deliveries ADD COLUMN agent_ack_cmd_id INTEGER; + +CREATE INDEX idx_msg_deliveries_agent_ack_cmd_id ON msg_deliveries(connection_id, agent_ack_cmd_id); +|] diff --git a/src/Simplex/Chat/Migrations/M20240324_custom_data.hs b/src/Simplex/Chat/Migrations/M20240324_custom_data.hs new file mode 100644 index 0000000000..bc1c4807eb --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240324_custom_data.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240324_custom_data where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240324_custom_data :: Query +m20240324_custom_data = + [sql| +ALTER TABLE contacts ADD COLUMN custom_data BLOB; +ALTER TABLE groups ADD COLUMN custom_data BLOB; +|] + +down_m20240324_custom_data :: Query +down_m20240324_custom_data = + [sql| +ALTER TABLE contacts DROP COLUMN custom_data; +ALTER TABLE groups DROP COLUMN custom_data; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 19c6ba24d0..11cbd8ae89 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -73,6 +73,7 @@ CREATE TABLE contacts( REFERENCES group_members(group_member_id) ON DELETE SET NULL, contact_grp_inv_sent INTEGER NOT NULL DEFAULT 0, contact_status TEXT NOT NULL DEFAULT 'active', + custom_data BLOB, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -120,7 +121,8 @@ CREATE TABLE groups( favorite INTEGER NOT NULL DEFAULT 0, send_rcpts INTEGER, via_group_link_uri_hash BLOB, - user_member_profile_sent_at TEXT, -- received + user_member_profile_sent_at TEXT, + custom_data BLOB, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -556,7 +558,6 @@ CREATE TABLE IF NOT EXISTS "msg_deliveries"( chat_ts TEXT NOT NULL DEFAULT(datetime('now')), created_at TEXT CHECK(created_at NOT NULL), updated_at TEXT CHECK(updated_at NOT NULL), - agent_ack_cmd_id INTEGER, -- broker_ts for received, created_at for sent delivery_status TEXT -- MsgDeliveryStatus ); CREATE TABLE note_folders( @@ -826,10 +827,6 @@ CREATE INDEX idx_contact_requests_updated_at ON contact_requests( ); CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at); CREATE INDEX idx_msg_deliveries_message_id ON "msg_deliveries"(message_id); -CREATE INDEX idx_msg_deliveries_agent_ack_cmd_id ON "msg_deliveries"( - connection_id, - agent_ack_cmd_id -); CREATE INDEX idx_msg_deliveries_agent_msg_id ON "msg_deliveries"( connection_id, agent_msg_id diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 5883c6042c..9bc31fa2c7 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -17,7 +17,6 @@ import qualified Data.Aeson.TH as JQ import Data.Bifunctor (first) import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteArray as BA -import qualified Data.ByteString.Base64.URL as U import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB @@ -50,6 +49,7 @@ import Simplex.Messaging.Agent.Env.SQLite (createAgentStore) import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, closeSQLiteStore, reopenSQLiteStore) import Simplex.Messaging.Client (defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C +import qualified Simplex.Messaging.Encoding.Base64.URL as U import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), CorrId (..), ProtoServerWithAuth (..), ProtocolServer (..)) diff --git a/src/Simplex/Chat/Mobile/WebRTC.hs b/src/Simplex/Chat/Mobile/WebRTC.hs index 537388b18b..b69203b651 100644 --- a/src/Simplex/Chat/Mobile/WebRTC.hs +++ b/src/Simplex/Chat/Mobile/WebRTC.hs @@ -17,7 +17,6 @@ import Data.Bifunctor (bimap) import qualified Data.ByteArray as BA import Data.ByteString (ByteString) import qualified Data.ByteString as B -import qualified Data.ByteString.Base64.URL as U import Data.Either (fromLeft) import Data.Word (Word8) import Foreign.C (CInt, CString, newCAString) @@ -26,6 +25,7 @@ import Foreign.StablePtr import Simplex.Chat.Controller (ChatController (..)) import Simplex.Chat.Mobile.Shared import qualified Simplex.Messaging.Crypto as C +import qualified Simplex.Messaging.Encoding.Base64.URL as U import UnliftIO (atomically) cChatEncryptMedia :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CString diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 857198a109..416b88599c 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -22,7 +22,6 @@ import Crypto.Random (getRandomBytes) import qualified Data.Aeson as J import qualified Data.Aeson.Types as JT import Data.ByteString (ByteString) -import qualified Data.ByteString.Base64.URL as B64U import Data.ByteString.Builder (Builder) import qualified Data.ByteString.Char8 as B import Data.Functor (($>)) @@ -50,12 +49,13 @@ import Simplex.Chat.Store.Files import Simplex.Chat.Store.Remote import Simplex.Chat.Store.Shared import Simplex.Chat.Types -import Simplex.Chat.Util (encryptFile) +import Simplex.Chat.Util (liftIOEither, encryptFile) import Simplex.FileTransfer.Description (FileDigest (..)) import Simplex.Messaging.Agent import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF +import qualified Simplex.Messaging.Encoding.Base64.URL as B64U import Simplex.Messaging.Encoding.String (StrEncoding (..)) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Transport (TLS, closeConnection, tlsUniq) @@ -95,7 +95,7 @@ discoveryTimeout = 60000000 -- * Desktop side -getRemoteHostClient :: ChatMonad m => RemoteHostId -> m RemoteHostClient +getRemoteHostClient :: RemoteHostId -> CM RemoteHostClient getRemoteHostClient rhId = do sessions <- asks remoteHostSessions liftIOEither . atomically $ @@ -106,7 +106,7 @@ getRemoteHostClient rhId = do where rhKey = RHId rhId -withRemoteHostSession :: ChatMonad m => RHKey -> SessionSeq -> (RemoteHostSession -> Either ChatError (a, RemoteHostSession)) -> m a +withRemoteHostSession :: RHKey -> SessionSeq -> (RemoteHostSession -> Either ChatError (a, RemoteHostSession)) -> CM a withRemoteHostSession rhKey sseq f = do sessions <- asks remoteHostSessions r <- @@ -121,7 +121,7 @@ withRemoteHostSession rhKey sseq f = do liftEither r -- | Transition session state with a 'RHNew' ID to an assigned 'RemoteHostId' -setNewRemoteHostId :: ChatMonad m => SessionSeq -> RemoteHostId -> m () +setNewRemoteHostId :: SessionSeq -> RemoteHostId -> CM () setNewRemoteHostId sseq rhId = do sessions <- asks remoteHostSessions liftIOEither . atomically $ do @@ -136,13 +136,13 @@ setNewRemoteHostId sseq rhId = do where err = pure . Left . ChatErrorRemoteHost RHNew -startRemoteHost :: ChatMonad m => Maybe (RemoteHostId, Bool) -> Maybe RCCtrlAddress -> Maybe Word16 -> m (NonEmpty RCCtrlAddress, Maybe RemoteHostInfo, RCSignedInvitation) +startRemoteHost :: Maybe (RemoteHostId, Bool) -> Maybe RCCtrlAddress -> Maybe Word16 -> CM (NonEmpty RCCtrlAddress, Maybe RemoteHostInfo, RCSignedInvitation) startRemoteHost rh_ rcAddrPrefs_ port_ = do (rhKey, multicast, remoteHost_, pairing) <- case rh_ of Just (rhId, multicast) -> do rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested - Nothing -> withAgent $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a + Nothing -> lift . withAgent' $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a sseq <- startRemoteHostSession rhKey ctrlAppInfo <- mkCtrlAppInfo (localAddrs, invitation, rchClient, vars) <- handleConnectError rhKey sseq . withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast rcAddrPrefs_ port_ @@ -170,18 +170,18 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do unless (isAppCompatible appVersion ctrlAppVersionRange) $ throwError $ RHEBadVersion appVersion when (encoding == PEKotlin && localEncoding == PESwift) $ throwError $ RHEProtocolError RPEIncompatibleEncoding pure hostInfo - handleConnectError :: ChatMonad m => RHKey -> SessionSeq -> m a -> m a + handleConnectError :: RHKey -> SessionSeq -> CM a -> CM a handleConnectError rhKey sessSeq action = action `catchChatError` \err -> do logError $ "startRemoteHost.rcConnectHost crashed: " <> tshow err cancelRemoteHostSession (Just (sessSeq, RHSRConnectionFailed err)) rhKey throwError err - handleHostError :: ChatMonad m => SessionSeq -> TVar RHKey -> m () -> m () + handleHostError :: SessionSeq -> TVar RHKey -> CM () -> CM () handleHostError sessSeq rhKeyVar action = action `catchChatError` \err -> do logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just (sessSeq, RHSRCrashed err)) - waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m () + waitForHostSession :: Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> CM () waitForHostSession remoteHost_ rhKey sseq rcAddr_ rhKeyVar vars = do (sessId, tls, vars') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars let sessionCode = verificationCode sessId @@ -203,7 +203,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do toView $ CRNewRemoteHost rhi -- set up HTTP transport and remote profile protocol disconnected <- toIO $ onDisconnected rhKey' sseq - httpClient <- liftEitherError (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls + httpClient <- liftError' (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls rhClient <- mkRemoteHostClient httpClient sessionKeys sessId storePath hostInfo pollAction <- async $ pollEvents remoteHostId rhClient withRemoteHostSession rhKey' sseq $ \case @@ -211,7 +211,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host toView $ CRRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}} - upsertRemoteHost :: ChatMonad m => RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> m RemoteHostInfo + upsertRemoteHost :: RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> CM RemoteHostInfo upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ rcAddr_ hostDeviceName sseq state = do KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_ case rhi_ of @@ -223,11 +223,11 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do Just rhi@RemoteHostInfo {remoteHostId} -> do withStore' $ \db -> updateHostPairing db remoteHostId hostDeviceName hostDhPubKey' rcAddr_ port_ pure (rhi :: RemoteHostInfo) {sessionState = Just state} - onDisconnected :: ChatMonad m => RHKey -> SessionSeq -> m () + onDisconnected :: RHKey -> SessionSeq -> CM () onDisconnected rhKey sseq = do logDebug $ "HTTP2 client disconnected: " <> tshow (rhKey, sseq) cancelRemoteHostSession (Just (sseq, RHSRDisconnected)) rhKey - pollEvents :: ChatMonad m => RemoteHostId -> RemoteHostClient -> m () + pollEvents :: RemoteHostId -> RemoteHostClient -> CM () pollEvents rhId rhClient = do oq <- asks outputQ forever $ do @@ -236,7 +236,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do httpError :: RemoteHostId -> HTTP2ClientError -> ChatError httpError rhId = ChatErrorRemoteHost (RHId rhId) . RHEProtocolError . RPEHTTP2 . tshow -startRemoteHostSession :: ChatMonad m => RHKey -> m SessionSeq +startRemoteHostSession :: RHKey -> CM SessionSeq startRemoteHostSession rhKey = do sessions <- asks remoteHostSessions nextSessionSeq <- asks remoteSessionSeq @@ -247,12 +247,12 @@ startRemoteHostSession rhKey = do sessionSeq <- stateTVar nextSessionSeq $ \s -> (s, s + 1) Right sessionSeq <$ TM.insert rhKey (sessionSeq, RHSessionStarting) sessions -closeRemoteHost :: ChatMonad m => RHKey -> m () +closeRemoteHost :: RHKey -> CM () closeRemoteHost rhKey = do logNote $ "Closing remote host session for " <> tshow rhKey cancelRemoteHostSession Nothing rhKey -cancelRemoteHostSession :: ChatMonad m => Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> m () +cancelRemoteHostSession :: Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> CM () cancelRemoteHostSession handlerInfo_ rhKey = do sessions <- asks remoteHostSessions crh <- asks currentRemoteHost @@ -299,7 +299,7 @@ cancelRemoteHost handlingError = \case randomStorePath :: IO FilePath randomStorePath = B.unpack . B64U.encode <$> getRandomBytes 12 -listRemoteHosts :: ChatMonad m => m [RemoteHostInfo] +listRemoteHosts :: CM [RemoteHostInfo] listRemoteHosts = do sessions <- chatReadVar remoteHostSessions map (rhInfo sessions) <$> withStore' getRemoteHosts @@ -307,7 +307,7 @@ listRemoteHosts = do rhInfo sessions rh@RemoteHost {remoteHostId} = remoteHostInfo rh $ rhsSessionState . snd <$> M.lookup (RHId remoteHostId) sessions -switchRemoteHost :: ChatMonad m => Maybe RemoteHostId -> m (Maybe RemoteHostInfo) +switchRemoteHost :: Maybe RemoteHostId -> CM (Maybe RemoteHostInfo) switchRemoteHost rhId_ = do rhi_ <- forM rhId_ $ \rhId -> do let rhKey = RHId rhId @@ -322,7 +322,7 @@ remoteHostInfo :: RemoteHost -> Maybe RemoteHostSessionState -> RemoteHostInfo remoteHostInfo RemoteHost {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_} sessionState = RemoteHostInfo {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_, sessionState} -deleteRemoteHost :: ChatMonad m => RemoteHostId -> m () +deleteRemoteHost :: RemoteHostId -> CM () deleteRemoteHost rhId = do RemoteHost {storePath} <- withStore (`getRemoteHost` rhId) chatReadVar remoteHostsFolder >>= \case @@ -333,7 +333,7 @@ deleteRemoteHost rhId = do Nothing -> logWarn "Local file store not available while deleting remote host" withStore' (`deleteRemoteHostRecord` rhId) -storeRemoteFile :: forall m. ChatMonad m => RemoteHostId -> Maybe Bool -> FilePath -> m CryptoFile +storeRemoteFile :: RemoteHostId -> Maybe Bool -> FilePath -> CM CryptoFile storeRemoteFile rhId encrypted_ localPath = do c@RemoteHostClient {encryptHostFiles, storePath} <- getRemoteHostClient rhId let encrypt = fromMaybe encryptHostFiles encrypted_ @@ -347,23 +347,23 @@ storeRemoteFile rhId encrypted_ localPath = do (if encrypt then renameFile else copyFile) filePath hPath pure (cf :: CryptoFile) {filePath = filePath'} where - encryptLocalFile :: m CryptoFile + encryptLocalFile :: CM CryptoFile encryptLocalFile = do - tmpDir <- getChatTempDirectory + tmpDir <- lift getChatTempDirectory createDirectoryIfMissing True tmpDir - tmpFile <- tmpDir `uniqueCombine` takeFileName localPath + tmpFile <- liftIO $ tmpDir `uniqueCombine` takeFileName localPath cfArgs <- atomically . CF.randomArgs =<< asks random liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs pure $ CryptoFile tmpFile $ Just cfArgs -getRemoteFile :: ChatMonad m => RemoteHostId -> RemoteFile -> m () +getRemoteFile :: RemoteHostId -> RemoteFile -> CM () getRemoteFile rhId rf = do c@RemoteHostClient {storePath} <- getRemoteHostClient rhId - dir <- ( storePath archiveFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar remoteHostsFolder) + dir <- lift $ ( storePath archiveFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar' remoteHostsFolder) createDirectoryIfMissing True dir liftRH rhId $ remoteGetFile c dir rf -processRemoteCommand :: ChatMonad m => RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteString -> m ChatResponse +processRemoteCommand :: RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteString -> CM ChatResponse processRemoteCommand remoteHostId c cmd s = case cmd of SendFile chatName f -> sendFile "/f" chatName f SendImage chatName f -> sendFile "/img" chatName f @@ -378,7 +378,7 @@ processRemoteCommand remoteHostId c cmd s = case cmd of maybe "" (\(CFArgs key nonce) -> "key=" <> strEncode key <> " nonce=" <> strEncode nonce <> " ") cryptoArgs <> encodeUtf8 (T.pack filePath) -liftRH :: ChatMonad m => RemoteHostId -> ExceptT RemoteProtocolError IO a -> m a +liftRH :: RemoteHostId -> ExceptT RemoteProtocolError IO a -> CM a liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError) -- * Mobile side @@ -386,7 +386,7 @@ liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError) -- ** QR/link -- | Use provided OOB link as an annouce -connectRemoteCtrlURI :: ChatMonad m => RCSignedInvitation -> m (Maybe RemoteCtrlInfo, CtrlAppInfo) +connectRemoteCtrlURI :: RCSignedInvitation -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo) connectRemoteCtrlURI signedInv = do verifiedInv <- maybe (throwError $ ChatErrorRemoteCtrl RCEBadInvitation) pure $ verifySignedInvitation signedInv sseq <- startRemoteCtrlSession @@ -394,7 +394,7 @@ connectRemoteCtrlURI signedInv = do -- ** Multicast -findKnownRemoteCtrl :: ChatMonad m => m () +findKnownRemoteCtrl :: CM () findKnownRemoteCtrl = do knownCtrls <- withStore' getRemoteCtrls pairings <- case nonEmpty knownCtrls of @@ -420,7 +420,7 @@ findKnownRemoteCtrl = do _ -> Left $ ChatErrorRemoteCtrl RCEBadState atomically $ putTMVar cmdOk () -confirmRemoteCtrl :: ChatMonad m => RemoteCtrlId -> m (RemoteCtrlInfo, CtrlAppInfo) +confirmRemoteCtrl :: RemoteCtrlId -> CM (RemoteCtrlInfo, CtrlAppInfo) confirmRemoteCtrl rcId = do session <- asks remoteCtrlSession (sseq, listener, found) <- liftIOEither $ atomically $ do @@ -438,7 +438,7 @@ confirmRemoteCtrl rcId = do -- ** Common -startRemoteCtrlSession :: ChatMonad m => m SessionSeq +startRemoteCtrlSession :: CM SessionSeq startRemoteCtrlSession = do session <- asks remoteCtrlSession nextSessionSeq <- asks remoteSessionSeq @@ -449,7 +449,7 @@ startRemoteCtrlSession = do sseq <- stateTVar nextSessionSeq $ \s -> (s, s + 1) Right sseq <$ writeTVar session (Just (sseq, RCSessionStarting)) -connectRemoteCtrl :: ChatMonad m => RCVerifiedInvitation -> SessionSeq -> m (Maybe RemoteCtrlInfo, CtrlAppInfo) +connectRemoteCtrl :: RCVerifiedInvitation -> SessionSeq -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo) connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq RCSRConnectionFailed "connectRemoteCtrl" $ do ctrlInfo@CtrlAppInfo {deviceName = ctrlDeviceName} <- parseCtrlAppInfo app v <- checkAppVersion ctrlInfo @@ -470,7 +470,7 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) where validateRemoteCtrl RCInvitation {idkey} RemoteCtrl {ctrlPairing = RCCtrlPairing {idPubKey}} = unless (idkey == idPubKey) $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError $ PRERemoteControl RCEIdentity - waitForCtrlSession :: ChatMonad m => Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> m () + waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM () waitForCtrlSession rc_ ctrlName rcsClient vars = do (uniq, tls, rcsWaitConfirmation) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars let sessionCode = verificationCode uniq @@ -489,18 +489,18 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) encryptFiles <- chatReadVar encryptLocalFiles pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles} -parseCtrlAppInfo :: ChatMonad m => JT.Value -> m CtrlAppInfo +parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo parseCtrlAppInfo ctrlAppInfo = do liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo -handleRemoteCommand :: forall m. ChatMonad m => (ByteString -> m ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> m () +handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> CM' () handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" - liftRC (tryRemoteError parseRequest) >>= \case + liftIO (tryRemoteError' parseRequest) >>= \case Right (getNext, rc) -> do - chatReadVar currentUser >>= \case + chatReadVar' currentUser >>= \case Nothing -> replyError $ ChatError CENoActiveUser - Just user -> processCommand user getNext rc `catchChatError` replyError + Just user -> processCommand user getNext rc `catchChatError'` replyError Left e -> reply $ RRProtocolError e where parseRequest :: ExceptT RemoteProtocolError IO (GetChunk, RemoteCommand) @@ -508,67 +508,72 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque (header, getNext) <- parseDecryptHTTP2Body encryption request reqBody (getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) replyError = reply . RRChatResponse . CRChatCmdError Nothing - processCommand :: User -> GetChunk -> RemoteCommand -> m () + processCommand :: User -> GetChunk -> RemoteCommand -> CM () processCommand user getNext = \case - RCSend {command} -> handleSend execChatCommand command >>= reply - RCRecv {wait = time} -> handleRecv time remoteOutputQ >>= reply - RCStoreFile {fileName, fileSize, fileDigest} -> handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply + RCSend {command} -> lift $ handleSend execChatCommand command >>= reply + RCRecv {wait = time} -> lift $ liftIO (handleRecv time remoteOutputQ) >>= reply + RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply RCGetFile {file} -> handleGetFile encryption user file replyWith - reply :: RemoteResponse -> m () + reply :: RemoteResponse -> CM' () reply = (`replyWith` \_ -> pure ()) - replyWith :: Respond m - replyWith rr attach = do - resp <- liftRC $ encryptEncodeHTTP2Body encryption $ J.encode rr - liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do - send resp - attach send - flush + replyWith :: Respond + replyWith rr attach = + liftIO (tryRemoteError' . encryptEncodeHTTP2Body encryption $ J.encode rr) >>= \case + Right resp -> liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do + send resp + attach send + flush + Left e -> toView' . CRChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e -takeRCStep :: ChatMonad m => RCStepTMVar a -> m a -takeRCStep = liftEitherError (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar +takeRCStep :: RCStepTMVar a -> CM a +takeRCStep = liftError' (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar type GetChunk = Int -> IO ByteString type SendChunk = Builder -> IO () -type Respond m = RemoteResponse -> (SendChunk -> IO ()) -> m () +type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM' () -liftRC :: ChatMonad m => ExceptT RemoteProtocolError IO a -> m a +liftRC :: ExceptT RemoteProtocolError IO a -> CM a liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError) tryRemoteError :: ExceptT RemoteProtocolError IO a -> ExceptT RemoteProtocolError IO (Either RemoteProtocolError a) tryRemoteError = tryAllErrors (RPEException . tshow) {-# INLINE tryRemoteError #-} -handleSend :: ChatMonad m => (ByteString -> m ChatResponse) -> Text -> m RemoteResponse +tryRemoteError' :: ExceptT RemoteProtocolError IO a -> IO (Either RemoteProtocolError a) +tryRemoteError' = tryAllErrors' (RPEException . tshow) +{-# INLINE tryRemoteError' #-} + +handleSend :: (ByteString -> CM' ChatResponse) -> Text -> CM' RemoteResponse handleSend execChatCommand command = do logDebug $ "Send: " <> tshow command -- execChatCommand checks for remote-allowed commands - -- convert errors thrown in ChatMonad into error responses to prevent aborting the protocol wrapper - RRChatResponse <$> execChatCommand (encodeUtf8 command) `catchError` (pure . CRChatError Nothing) + -- convert errors thrown in execChatCommand into error responses to prevent aborting the protocol wrapper + RRChatResponse <$> execChatCommand (encodeUtf8 command) -handleRecv :: MonadUnliftIO m => Int -> TBQueue ChatResponse -> m RemoteResponse +handleRecv :: Int -> TBQueue ChatResponse -> IO RemoteResponse handleRecv time events = do logDebug $ "Recv: " <> tshow time RRChatEvent <$> (timeout time . atomically $ readTBQueue events) -- TODO this command could remember stored files and return IDs to allow removing files that are not needed. -- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files). -handleStoreFile :: forall m. ChatMonad m => RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> m RemoteResponse +handleStoreFile :: RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse handleStoreFile encryption fileName fileSize fileDigest getChunk = - either RRProtocolError RRFileStored <$> (chatReadVar filesFolder >>= storeFile) + either RRProtocolError RRFileStored <$> (chatReadVar' filesFolder >>= storeFile) where - storeFile :: Maybe FilePath -> m (Either RemoteProtocolError FilePath) + storeFile :: Maybe FilePath -> CM' (Either RemoteProtocolError FilePath) storeFile = \case Just ff -> takeFileName <$$> storeFileTo ff Nothing -> storeFileTo =<< getDefaultFilesFolder - storeFileTo :: FilePath -> m (Either RemoteProtocolError FilePath) - storeFileTo dir = liftRC . tryRemoteError $ do - filePath <- dir `uniqueCombine` fileName + storeFileTo :: FilePath -> CM' (Either RemoteProtocolError FilePath) + storeFileTo dir = liftIO . tryRemoteError' $ do + filePath <- liftIO $ dir `uniqueCombine` fileName receiveEncryptedFile encryption getChunk fileSize fileDigest filePath pure filePath -handleGetFile :: ChatMonad m => RemoteCrypto -> User -> RemoteFile -> Respond m -> m () +handleGetFile :: RemoteCrypto -> User -> RemoteFile -> Respond -> CM () handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do logDebug $ "GetFile: " <> tshow filePath unless (userId == commandUserId) $ throwChatError $ CEDifferentActiveUser {commandUserId, activeUserId = userId} @@ -577,13 +582,13 @@ handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileI cf <- getLocalCryptoFile db commandUserId fileId sent unless (cf == cf') $ throwError $ SEFileNotFound fileId liftRC (tryRemoteError $ getFileInfo path) >>= \case - Left e -> reply (RRProtocolError e) $ \_ -> pure () + Left e -> lift $ reply (RRProtocolError e) $ \_ -> pure () Right (fileSize, fileDigest) -> - withFile path ReadMode $ \h -> do + ExceptT . withFile path ReadMode $ \h -> runExceptT $ do encFile <- liftRC $ prepareEncryptedFile encryption (h, fileSize) - reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile + lift $ reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile -listRemoteCtrls :: ChatMonad m => m [RemoteCtrlInfo] +listRemoteCtrls :: CM [RemoteCtrlInfo] listRemoteCtrls = do session <- snd <$$> chatReadVar remoteCtrlSession let rcId = sessionRcId =<< session @@ -604,7 +609,7 @@ remoteCtrlInfo RemoteCtrl {remoteCtrlId, ctrlDeviceName} sessionState = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionState} -- | Take a look at emoji of tlsunique, commit pairing, and start session server -verifyRemoteCtrlSession :: ChatMonad m => (ByteString -> m ChatResponse) -> Text -> m RemoteCtrlInfo +verifyRemoteCtrlSession :: (ByteString -> CM' ChatResponse) -> Text -> CM RemoteCtrlInfo verifyRemoteCtrlSession execChatCommand sessCode' = do (sseq, client, ctrlName, sessionCode, vars) <- chatReadVar remoteCtrlSession >>= \case @@ -619,14 +624,15 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do rc@RemoteCtrl {remoteCtrlId} <- upsertRemoteCtrl ctrlName rcCtrlPairing remoteOutputQ <- asks (tbqSize . config) >>= newTBQueueIO encryption <- mkCtrlRemoteCrypto sessionKeys $ tlsUniq tls - http2Server <- async $ attachHTTP2Server tls $ handleRemoteCommand execChatCommand encryption remoteOutputQ + cc <- ask + http2Server <- liftIO . async $ attachHTTP2Server tls $ \req -> handleRemoteCommand execChatCommand encryption remoteOutputQ req `runReaderT` cc void . forkIO $ monitor sseq http2Server updateRemoteCtrlSession sseq $ \case RCSessionPendingConfirmation {} -> Right RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ} _ -> Left $ ChatErrorRemoteCtrl RCEBadState pure $ remoteCtrlInfo rc $ Just RCSConnected {sessionCode = tlsSessionCode tls} where - upsertRemoteCtrl :: ChatMonad m => Text -> RCCtrlPairing -> m RemoteCtrl + upsertRemoteCtrl :: Text -> RCCtrlPairing -> CM RemoteCtrl upsertRemoteCtrl ctrlName rcCtrlPairing = withStore $ \db -> do rc_ <- liftIO $ getRemoteCtrlByFingerprint db (ctrlFingerprint rcCtrlPairing) case rc_ of @@ -635,16 +641,16 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do let dhPrivKey' = dhPrivKey rcCtrlPairing liftIO $ updateRemoteCtrl db rc ctrlName dhPrivKey' pure rc {ctrlDeviceName = ctrlName, ctrlPairing = ctrlPairing {dhPrivKey = dhPrivKey'}} - monitor :: ChatMonad m => SessionSeq -> Async () -> m () + monitor :: SessionSeq -> Async () -> CM () monitor sseq server = do res <- waitCatch server logInfo $ "HTTP2 server stopped: " <> tshow res cancelActiveRemoteCtrl $ Just (sseq, RCSRDisconnected) -stopRemoteCtrl :: ChatMonad m => m () +stopRemoteCtrl :: CM () stopRemoteCtrl = cancelActiveRemoteCtrl Nothing -handleCtrlError :: ChatMonad m => SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> m a -> m a +handleCtrlError :: SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> CM a -> CM a handleCtrlError sseq mkReason name action = action `catchChatError` \e -> do logError $ name <> " remote ctrl error: " <> tshow e @@ -652,7 +658,7 @@ handleCtrlError sseq mkReason name action = throwError e -- | Stop session controller, unless session update key is present but stale -cancelActiveRemoteCtrl :: ChatMonad m => Maybe (SessionSeq, RemoteCtrlStopReason) -> m () +cancelActiveRemoteCtrl :: Maybe (SessionSeq, RemoteCtrlStopReason) -> CM () cancelActiveRemoteCtrl handlerInfo_ = handleAny (logError . tshow) $ do var <- asks remoteCtrlSession session_ <- @@ -685,18 +691,18 @@ cancelRemoteCtrl handlingError = \case cancelCtrlClient rcsClient closeConnection tls -deleteRemoteCtrl :: ChatMonad m => RemoteCtrlId -> m () +deleteRemoteCtrl :: RemoteCtrlId -> CM () deleteRemoteCtrl rcId = do checkNoRemoteCtrlSession -- TODO check it exists withStore' (`deleteRemoteCtrlRecord` rcId) -checkNoRemoteCtrlSession :: ChatMonad m => m () +checkNoRemoteCtrlSession :: CM () checkNoRemoteCtrlSession = chatReadVar remoteCtrlSession >>= maybe (pure ()) (\_ -> throwError $ ChatErrorRemoteCtrl RCEBusy) -- | Transition controller to a new state, unless session update key is stale -updateRemoteCtrlSession :: ChatMonad m => SessionSeq -> (RemoteCtrlSession -> Either ChatError RemoteCtrlSession) -> m () +updateRemoteCtrlSession :: SessionSeq -> (RemoteCtrlSession -> Either ChatError RemoteCtrlSession) -> CM () updateRemoteCtrlSession sseq state = do session <- asks remoteCtrlSession r <- atomically $ do diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index 7b3d70ff97..fe07a940ae 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -46,7 +46,7 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFi import Simplex.Messaging.Transport.Buffer (getBuffered) import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..), HTTP2BodyChunk, getBodyChunk) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2Response (..), closeHTTP2Client, sendRequestDirect) -import Simplex.Messaging.Util (liftEitherError, liftEitherWith, liftError, tshow) +import Simplex.Messaging.Util (liftError', liftEitherWith, liftError, tshow) import Simplex.RemoteControl.Client (xrcpBlockSize) import qualified Simplex.RemoteControl.Client as RC import Simplex.RemoteControl.Types (CtrlSessKeys (..), HostSessKeys (..), RCErrorType (..), SessionCode) @@ -75,7 +75,7 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) -- * Client side / desktop -mkRemoteHostClient :: ChatMonad m => HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> m RemoteHostClient +mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> CM RemoteHostClient mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do drg <- asks random counter <- newTVarIO 1 @@ -92,15 +92,15 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc storePath } -mkCtrlRemoteCrypto :: ChatMonad m => CtrlSessKeys -> SessionCode -> m RemoteCrypto +mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> CM RemoteCrypto mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do drg <- asks random counter <- newTVarIO 1 let signatures = RSVerify {idPubKey, sessPubKey} pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} -closeRemoteHostClient :: MonadIO m => RemoteHostClient -> m () -closeRemoteHostClient RemoteHostClient {httpClient} = liftIO $ closeHTTP2Client httpClient +closeRemoteHostClient :: RemoteHostClient -> IO () +closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClient -- ** Commands @@ -141,7 +141,7 @@ sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} file_ cmd = do encFile_ <- mapM (prepareEncryptedFile encryption) file_ req <- httpRequest encFile_ <$> encryptEncodeHTTP2Body encryption (J.encode cmd) - HTTP2Response {response, respBody} <- liftEitherError (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing + HTTP2Response {response, respBody} <- liftError' (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing (header, getNext) <- parseDecryptHTTP2Body encryption response respBody rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding pure (getNext, rr) @@ -271,7 +271,7 @@ parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2 where getSig = do len <- liftIO $ B.head <$> getNext 1 - liftEitherError RPEInvalidBody $ C.decodeSignature <$> getNext (fromIntegral len) + liftError' RPEInvalidBody $ C.decodeSignature <$> getNext (fromIntegral len) verifySig key sig hc' = do let signed = BA.convert $ CH.hashFinalize hc' unless (C.verify' key sig signed) $ throwError $ PRERemoteControl RCECtrlAuth diff --git a/src/Simplex/Chat/Remote/RevHTTP.hs b/src/Simplex/Chat/Remote/RevHTTP.hs index a37d77e20a..44f3e50b0d 100644 --- a/src/Simplex/Chat/Remote/RevHTTP.hs +++ b/src/Simplex/Chat/Remote/RevHTTP.hs @@ -13,19 +13,17 @@ import Simplex.Messaging.Transport.HTTP2 (defaultHTTP2BufferSize, getHTTP2Body) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2ClientError (..), attachHTTP2Client, bodyHeadSize, connTimeout, defaultHTTP2ClientConfig) import Simplex.Messaging.Transport.HTTP2.Server (HTTP2Request (..), runHTTP2ServerWith) import Simplex.RemoteControl.Discovery -import UnliftIO attachRevHTTP2Client :: IO () -> TLS -> IO (Either HTTP2ClientError HTTP2Client) attachRevHTTP2Client disconnected = attachHTTP2Client config ANY_ADDR_V4 "0" disconnected defaultHTTP2BufferSize where config = defaultHTTP2ClientConfig {bodyHeadSize = doNotPrefetchHead, connTimeout = maxBound} -attachHTTP2Server :: MonadUnliftIO m => TLS -> (HTTP2Request -> m ()) -> m () -attachHTTP2Server tls processRequest = do - withRunInIO $ \unlift -> - runHTTP2ServerWith defaultHTTP2BufferSize ($ tls) $ \sessionId r sendResponse -> do - reqBody <- getHTTP2Body r doNotPrefetchHead - unlift $ processRequest HTTP2Request {sessionId, request = r, reqBody, sendResponse} +attachHTTP2Server :: TLS -> (HTTP2Request -> IO ()) -> IO () +attachHTTP2Server tls processRequest = + runHTTP2ServerWith defaultHTTP2BufferSize ($ tls) $ \sessionId r sendResponse -> do + reqBody <- getHTTP2Body r doNotPrefetchHead + processRequest HTTP2Request {sessionId, request = r, reqBody, sendResponse} -- | Suppress storing initial chunk in bodyHead, forcing clients and servers to stream chunks doNotPrefetchHead :: Int diff --git a/src/Simplex/Chat/Remote/Transport.hs b/src/Simplex/Chat/Remote/Transport.hs index 1c9c3f08eb..774aeccda2 100644 --- a/src/Simplex/Chat/Remote/Transport.hs +++ b/src/Simplex/Chat/Remote/Transport.hs @@ -15,7 +15,7 @@ import Simplex.FileTransfer.Transport (ReceiveFileError (..), receiveSbFile, sen import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto.Lazy as LC import Simplex.Messaging.Encoding -import Simplex.Messaging.Util (liftEitherError, liftEitherWith) +import Simplex.Messaging.Util (liftError', liftEitherWith) import Simplex.RemoteControl.Types (RCErrorType (..)) import UnliftIO import UnliftIO.Directory (getFileSize) @@ -37,11 +37,11 @@ receiveEncryptedFile :: RemoteCrypto -> (Int -> IO ByteString) -> Word32 -> File receiveEncryptedFile RemoteCrypto {hybridKey} getChunk fileSize fileDigest toPath = do c <- liftIO $ getChunk 1 unless (c == "\x01") $ throwError RPENoFile - nonce <- liftEitherError RPEInvalidBody $ smpDecode <$> getChunk 24 - size <- liftEitherError RPEInvalidBody $ smpDecode <$> getChunk 4 + nonce <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 24 + size <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 4 unless (size == fileSize + fromIntegral C.authTagSize) $ throwError RPEFileSize sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.kcbInit hybridKey nonce - liftEitherError fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize + liftError' fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize digest <- liftIO $ LC.sha512Hash <$> LB.readFile toPath unless (FileDigest digest == fileDigest) $ throwError RPEFileDigest where diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index f8e9fa3401..6584aabb0a 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -75,19 +75,19 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do [sql| SELECT c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.via_group, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, - p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent + p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent, c.custom_data FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id WHERE c.user_id = ? AND c.contact_id = ? AND c.deleted = 0 |] (userId, contactId) - toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool)] -> Either StoreError Contact - toContact' contactId conn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)] = + toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool, Maybe CustomData)] -> Either StoreError Contact + toContact' contactId conn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData)] = let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn activeConn = Just conn - in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent} + in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData} toContact' _ _ _ = Left $ SEInternalError "referenced contact not found" getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember_ groupMemberId c = ExceptT $ do @@ -99,7 +99,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 47174a59a6..ba2586b0ca 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -68,6 +68,7 @@ module Simplex.Chat.Store.Direct updateContactSettings, setConnConnReqInv, resetContactConnInitiated, + setContactCustomData, ) where @@ -175,7 +176,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, @@ -221,7 +222,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p let profile = toLocalProfile profileId p localAlias userPreferences = emptyChatPrefs mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn - pure $ Contact {contactId, localDisplayName, profile, activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False} + pure $ Contact {contactId, localDisplayName, profile, activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False, customData = Nothing} deleteContactConnectionsAndFiles :: DB.Connection -> UserId -> Contact -> IO () deleteContactConnectionsAndFiles db userId Contact {contactId} = do @@ -578,7 +579,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, @@ -724,7 +725,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences} contactId <- insertedRowId db conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn - pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt, contactGroupMemberId = Nothing, contactGrpInvSent = False} + pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt, contactGroupMemberId = Nothing, contactGrpInvSent = False, customData = Nothing} getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64 getContactIdByName db User {userId} cName = @@ -743,7 +744,7 @@ getContact_ db vr user@User {userId} contactId deleted = SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, @@ -883,3 +884,8 @@ resetContactConnInitiated db User {userId} Connection {connId} = do WHERE user_id = ? AND connection_id = ? |] (updatedAt, userId, connId) + +setContactCustomData :: DB.Connection -> User -> Contact -> Maybe CustomData -> IO () +setContactCustomData db User {userId} Contact {contactId} customData = do + updatedAt <- getCurrentTime + DB.execute db "UPDATE contacts SET custom_data = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (customData, updatedAt, userId, contactId) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 254b8dab59..832b928012 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -116,6 +116,7 @@ module Simplex.Chat.Store.Groups createNewUnknownGroupMember, updateUnknownMemberAnnounced, updateUserMemberProfileSentAt, + setGroupCustomData, ) where @@ -148,19 +149,19 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version import UnliftIO.STM -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe CustomData) :. GroupMemberRow type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) toGroupInfo :: (PQSupport -> VersionRangeChat) -> Int64 -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. userMemberRow) = +toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, customData) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr PQSupportOff} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} - in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt} + in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, customData} toGroupMember :: Int64 -> GroupMemberRow -> GroupMember toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = @@ -271,7 +272,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -344,7 +345,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, - userMemberProfileSentAt = Just currentTs + userMemberProfileSentAt = Just currentTs, + customData = Nothing } -- | creates a new group record for the group the current user was invited to, or returns an existing one @@ -409,7 +411,8 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, - userMemberProfileSentAt = Just currentTs + userMemberProfileSentAt = Just currentTs, + customData = Nothing }, groupMemberId ) @@ -628,7 +631,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g @@ -1293,7 +1296,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1389,7 +1392,7 @@ getGroupInfo db vr User {userId, userContactId} groupId = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1951,7 +1954,7 @@ createMemberContact authErrCounter = 0 } mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn - pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False} + pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, customData = Nothing} getMemberContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation) getMemberContact db vr user contactId = do @@ -1988,7 +1991,7 @@ createMemberContactInvited contactId <- createContactUpdateMember currentTs userPreferences ctConn <- createMemberContactConn_ db user connIds gInfo mConn contactId subMode let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn - mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False} + mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False, customData = Nothing} m' = m {memberContactId = Just contactId} pure (mCt', m') where @@ -2188,3 +2191,8 @@ updateUserMemberProfileSentAt db User {userId} GroupInfo {groupId} sentTs = db "UPDATE groups SET user_member_profile_sent_at = ? WHERE user_id = ? AND group_id = ?" (sentTs, userId, groupId) + +setGroupCustomData :: DB.Connection -> User -> GroupInfo -> Maybe CustomData -> IO () +setGroupCustomData db User {userId} GroupInfo {groupId} customData = do + updatedAt <- getCurrentTime + DB.execute db "UPDATE groups SET custom_data = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (customData, updatedAt, userId, groupId) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index bf74add6db..fe89d7f506 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -25,7 +25,6 @@ module Simplex.Chat.Store.Messages createNewMessageAndRcvMsgDelivery, createNewRcvMessage, updateSndMsgDeliveryStatus, - updateRcvMsgDeliveryStatus, createPendingGroupMessage, getPendingGroupMessages, deletePendingGroupMessage, @@ -42,6 +41,7 @@ module Simplex.Chat.Store.Messages getDirectChatItemLast, getAllChatItems, getAChatItem, + getAChatItemBySharedMsgId, updateDirectChatItem, updateDirectChatItem', addInitialAndNewCIVersions, @@ -211,7 +211,7 @@ createSndMsgDelivery db SndMsgDelivery {connId, agentMsgId} messageId = do insertedRowId db createNewMessageAndRcvMsgDelivery :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> RcvMsgDelivery -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage -createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} authorGroupMemberId_ = do +createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta} authorGroupMemberId_ = do msg@RcvMessage {msgId} <- createNewRcvMessage db connOrGroupId newMessage sharedMsgId_ authorGroupMemberId_ Nothing liftIO $ do currentTs <- getCurrentTime @@ -219,10 +219,10 @@ createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMs db [sql| INSERT INTO msg_deliveries - (message_id, connection_id, agent_msg_id, agent_msg_meta, agent_ack_cmd_id, chat_ts, created_at, updated_at, delivery_status) - VALUES (?,?,?,?,?,?,?,?,?) + (message_id, connection_id, agent_msg_id, agent_msg_meta, chat_ts, created_at, updated_at, delivery_status) + VALUES (?,?,?,?,?,?,?,?) |] - (msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, agentAckCmdId, snd $ broker agentMsgMeta, currentTs, currentTs, MDSRcvAgent) + (msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, snd $ broker agentMsgMeta, currentTs, currentTs, MDSRcvAgent) pure msg createNewRcvMessage :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage @@ -273,18 +273,6 @@ updateSndMsgDeliveryStatus db connId agentMsgId sndMsgDeliveryStatus = do |] (sndMsgDeliveryStatus, currentTs, connId, agentMsgId) -updateRcvMsgDeliveryStatus :: DB.Connection -> Int64 -> CommandId -> MsgDeliveryStatus 'MDRcv -> IO () -updateRcvMsgDeliveryStatus db connId cmdId rcvMsgDeliveryStatus = do - currentTs <- getCurrentTime - DB.execute - db - [sql| - UPDATE msg_deliveries - SET delivery_status = ?, updated_at = ? - WHERE connection_id = ? AND agent_ack_cmd_id = ? - |] - (rcvMsgDeliveryStatus, currentTs, connId, cmdId) - createPendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> Maybe Int64 -> IO () createPendingGroupMessage db groupMemberId messageId introId_ = do currentTs <- getCurrentTime @@ -2215,6 +2203,15 @@ getAChatItem db vr user chatRef itemId = case chatRef of pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci _ -> throwError $ SEChatItemNotFound itemId +getAChatItemBySharedMsgId :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> SharedMsgId -> ExceptT StoreError IO AChatItem +getAChatItemBySharedMsgId db user cd sharedMsgId = case cd of + CDDirectRcv ct@Contact {contactId} -> do + (CChatItem msgDir ci) <- getDirectChatItemBySharedMsgId db user contactId sharedMsgId + pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci + CDGroupRcv g@GroupInfo {groupId} GroupMember {groupMemberId} -> do + (CChatItem msgDir ci) <- getGroupChatItemBySharedMsgId db user groupId groupMemberId sharedMsgId + pure $ AChatItem SCTGroup msgDir (GroupChat g) ci + getChatItemVersions :: DB.Connection -> ChatItemId -> IO [ChatItemVersion] getChatItemVersions db itemId = do map toChatItemVersion diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index d8bdbd6fd3..e351f0f27a 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -102,6 +102,8 @@ import Simplex.Chat.Migrations.M20240214_redirect_file_id import Simplex.Chat.Migrations.M20240222_app_settings import Simplex.Chat.Migrations.M20240226_users_restrict import Simplex.Chat.Migrations.M20240228_pq +import Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id +import Simplex.Chat.Migrations.M20240324_custom_data import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -203,7 +205,9 @@ schemaMigrations = ("20240214_redirect_file_id", m20240214_redirect_file_id, Just down_m20240214_redirect_file_id), ("20240222_app_settings", m20240222_app_settings, Just down_m20240222_app_settings), ("20240226_users_restrict", m20240226_users_restrict, Just down_m20240226_users_restrict), - ("20240228_pq", m20240228_pq, Just down_m20240228_pq) + ("20240228_pq", m20240228_pq, Just down_m20240228_pq), + ("20240313_drop_agent_ack_cmd_id", m20240313_drop_agent_ack_cmd_id, Just down_m20240313_drop_agent_ack_cmd_id), + ("20240324_custom_data", m20240324_custom_data, Just down_m20240324_custom_data) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index fd628d09ee..dc75ad50e9 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -18,7 +18,6 @@ import Control.Monad.Except import Control.Monad.IO.Class import Crypto.Random (ChaChaDRG) import qualified Data.Aeson.TH as J -import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust, listToMaybe) @@ -39,6 +38,7 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..)) import qualified Simplex.Messaging.Crypto.Ratchet as CR +import qualified Simplex.Messaging.Encoding.Base64 as B64 import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util (allFinally) @@ -371,16 +371,16 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = |] [":user_id" := userId, ":profile_id" := profileId] -type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool) +type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool, Maybe CustomData) toContact :: (PQSupport -> VersionRangeChat) -> User -> ContactRow :. MaybeConnectionRow -> Contact -toContact vr user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)) :. connRow) = +toContact vr user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} activeConn = toMaybeConnection vr connRow chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} incognito = maybe False connIncognito activeConn mergedPreferences = contactUserPreferences user userPreferences preferences incognito - in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent} + in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData} getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile getProfileById db userId profileId = diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs new file mode 100644 index 0000000000..2b26bb1d66 --- /dev/null +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} + +module Simplex.Chat.Terminal.Main where + +import Control.Concurrent (forkIO, threadDelay) +import Control.Concurrent.STM +import Control.Monad +import Data.Time.Clock (getCurrentTime) +import Data.Time.LocalTime (getCurrentTimeZone) +import Network.Socket +import Simplex.Chat.Controller (ChatConfig, ChatController (..), ChatResponse (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Core +import Simplex.Chat.Options +import Simplex.Chat.Terminal +import Simplex.Chat.View (serializeChatResponse) +import Simplex.Messaging.Client (NetworkConfig (..)) +import System.Directory (getAppUserDataDirectory) +import System.Exit (exitFailure) +import System.Terminal (withTerminal) + +simplexChatCLI :: ChatConfig -> Maybe (ServiceName -> ChatConfig -> ChatOpts -> IO ()) -> IO () +simplexChatCLI cfg server_ = do + appDir <- getAppUserDataDirectory "simplex" + opts@ChatOpts {chatCmd, chatServerPort} <- getChatOpts appDir "simplex_v1" + if null chatCmd + then case chatServerPort of + Just chatPort -> case server_ of + Just server -> server chatPort cfg opts + Nothing -> putStrLn "Not allowed to run as a WebSockets server" >> exitFailure + _ -> runCLI opts + else simplexChatCore cfg opts $ runCommand opts + where + runCLI opts = do + welcome opts + t <- withTerminal pure + simplexChatTerminal cfg opts t + runCommand ChatOpts {chatCmd, chatCmdLog, chatCmdDelay} user cc = do + when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do + (_, _, r') <- atomically . readTBQueue $ outputQ cc + case r' of + CRNewChatItem {} -> printResponse r' + _ -> when (chatCmdLog == CCLAll) $ printResponse r' + sendChatCmdStr cc chatCmd >>= printResponse + threadDelay $ chatCmdDelay * 1000000 + where + printResponse r = do + ts <- getCurrentTime + tz <- getCurrentTimeZone + rh <- readTVarIO $ currentRemoteHost cc + putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r + +welcome :: ChatOpts -> IO () +welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} = + mapM_ + putStrLn + [ versionString versionNumber, + "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db", + maybe + "direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050" + (("using SOCKS5 proxy " <>) . show) + (socksProxy networkConfig), + "type \"/help\" or \"/h\" for usage info" + ] diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index f16913439d..e419f8c4cb 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -174,10 +174,25 @@ data Contact = Contact updatedAt :: UTCTime, chatTs :: Maybe UTCTime, contactGroupMemberId :: Maybe GroupMemberId, - contactGrpInvSent :: Bool + contactGrpInvSent :: Bool, + customData :: Maybe CustomData } deriving (Eq, Show) +newtype CustomData = CustomData J.Object + deriving (Eq, Show) + +instance ToJSON CustomData where + toJSON (CustomData v) = toJSON v + toEncoding (CustomData v) = toEncoding v + +instance FromJSON CustomData where + parseJSON = J.withObject "CustomData" (pure . CustomData) + +instance ToField CustomData where toField (CustomData v) = toField $ J.encode v + +instance FromField CustomData where fromField = fromBlobField_ J.eitherDecodeStrict + contactConn :: Contact -> Maybe Connection contactConn Contact {activeConn} = activeConn @@ -356,7 +371,8 @@ data GroupInfo = GroupInfo createdAt :: UTCTime, updatedAt :: UTCTime, chatTs :: Maybe UTCTime, - userMemberProfileSentAt :: Maybe UTCTime + userMemberProfileSentAt :: Maybe UTCTime, + customData :: Maybe CustomData } deriving (Eq, Show) @@ -1551,7 +1567,7 @@ data CommandFunction | CFJoinConn | CFAllowConn | CFAcceptContact - | CFAckMessage + | CFAckMessage -- not used | CFDeleteConn -- not used deriving (Eq, Show) diff --git a/src/Simplex/Chat/Util.hs b/src/Simplex/Chat/Util.hs index eacaf8d7ef..2b2bd599ae 100644 --- a/src/Simplex/Chat/Util.hs +++ b/src/Simplex/Chat/Util.hs @@ -1,6 +1,6 @@ {-# LANGUAGE TupleSections #-} -module Simplex.Chat.Util (week, encryptFile, chunkSize, shuffle) where +module Simplex.Chat.Util (week, encryptFile, chunkSize, liftIOEither, shuffle) where import Control.Monad import Control.Monad.Except @@ -42,3 +42,7 @@ shuffle xs = map snd . sortBy (comparing fst) <$> mapM (\x -> (,x) <$> random) x where random :: IO Word16 random = randomRIO (0, 65535) + +liftIOEither :: (MonadIO m, MonadError e m) => IO (Either e a) -> m a +liftIOEither a = liftIO a >>= liftEither +{-# INLINE liftIOEither #-} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 50dc151aa4..65a6626308 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -183,8 +183,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m] CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"] - CRRcvFileDescrReady _ _ -> [] - CRRcvFileDescrNotReady _ _ -> [] + CRRcvFileDescrReady _ _ _ _ -> [] CRRcvFileProgressXFTP {} -> [] CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci CRRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft @@ -391,6 +390,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)] CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)] CRTimedAction _ _ -> [] + CRCustomChatResponse u r -> ttyUser' u $ [plain r] where ttyUser :: User -> [StyledString] -> [StyledString] ttyUser user@User {showNtfs, activeUser} ss @@ -1167,7 +1167,7 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} = ] viewContactInfo :: Contact -> Maybe ConnectionStats -> Maybe Profile -> [StyledString] -viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn} stats incognitoProfile = +viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn, customData} stats incognitoProfile = ["contact ID: " <> sShow contactId] <> maybe [] viewConnectionStats stats <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink @@ -1179,12 +1179,17 @@ viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, conta <> [viewConnectionVerified (contactSecurityCode ct)] <> ["quantum resistant end-to-end encryption" | contactPQEnabled ct == CR.PQEncOn] <> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn + <> viewCustomData customData viewGroupInfo :: GroupInfo -> GroupSummary -> [StyledString] -viewGroupInfo GroupInfo {groupId} s = +viewGroupInfo GroupInfo {groupId, customData} s = [ "group ID: " <> sShow groupId, "current members: " <> sShow (currentMembers s) ] + <> viewCustomData customData + +viewCustomData :: Maybe CustomData -> [StyledString] +viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> plain (LB.toStrict . J.encode $ J.Object v)]) viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString] viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias, contactLink}, activeConn} stats = diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 1054a1d8eb..b78d36f489 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -522,7 +522,7 @@ testNotApprovedBadRoles tmp = let approve = "/approve 1:privacy 1" superUser #> ("@SimpleX-Directory " <> approve) superUser <# ("SimpleX-Directory> > " <> approve) - superUser <## " Group is not approved: user is not an owner." + superUser <## " Group is not approved: SimpleX-Directory is not an admin." groupNotFound cath "privacy" bob ##> "/mr privacy SimpleX-Directory admin" bob <## "#privacy: you changed the role of SimpleX-Directory from member to admin" diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 8e9d11d91f..a827236a15 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -87,7 +87,7 @@ testOpts = testCoreOpts :: CoreChatOpts testCoreOpts = CoreChatOpts - { dbFilePrefix = undefined, + { dbFilePrefix = "./simplex_v1", dbKey = "", -- dbKey = "this is a pass-phrase to encrypt the database", smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], @@ -413,6 +413,8 @@ serverCfg = allowNewQueues = True, -- server password is disabled as otherwise v1 tests fail newQueueBasicAuth = Nothing, -- Just "server_password", + controlPortUserAuth = Nothing, + controlPortAdminAuth = Nothing, messageExpiration = Just defaultMessageExpiration, inactiveClientExpiration = Just defaultInactiveClientExpiration, caCertificateFile = "tests/fixtures/tls/ca.crt", @@ -448,6 +450,8 @@ xftpServerConfig = allowedChunkSizes = [kb 64, kb 128, kb 256, mb 1, mb 4], allowNewFiles = True, newFileBasicAuth = Nothing, + controlPortUserAuth = Nothing, + controlPortAdminAuth = Nothing, fileExpiration = Just defaultFileExpiration, fileTimeout = 10000000, inactiveClientExpiration = Just defaultInactiveClientExpiration, diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 4e06f68fb6..4b0389ab90 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2997,10 +2997,15 @@ testPQEnableContactCompression = bob ##> "/pq @alice on" bob <## "alice: enable quantum resistant end-to-end encryption" PQSupportOn <- bob `pqSupportForCt` 2 + threadDelay 300000 (alice, "lrg 1", v) \:#> (bob, v') + threadDelay 300000 (bob, "lrg 2", v') \:#> (alice, v') + threadDelay 300000 (alice, "lrg 3", v') \:#> (bob, v') + threadDelay 300000 (bob, "lrg 4", v') \:#> (alice, v') + threadDelay 300000 (alice, "lrg 5", v') +:#> (bob, v') PQEncOff <- alice `pqForContact` 2 PQEncOff <- bob `pqForContact` 2 diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index bf9b445925..77bac11145 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -871,6 +871,9 @@ testGroupRemoveAdd = testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath + + threadDelay 100000 + -- remove member alice ##> "/rm team bob" concurrentlyN_ @@ -880,6 +883,9 @@ testGroupRemoveAdd = bob <## "use /d #team to delete the group", cath <## "#team: alice removed bob from the group" ] + + threadDelay 100000 + alice ##> "/a team bob" alice <## "invitation to join the group #team sent to bob" bob <## "#team_1: alice invites you to join the group as member" @@ -1916,6 +1922,8 @@ testGroupLink = (alice <# "#team cath> hey team") (bob <# "#team cath> hey team") + threadDelay 100000 + -- leaving team removes link alice ##> "/l team" concurrentlyN_ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 3b0748e7d0..13812cbaaa 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -14,7 +14,6 @@ import Control.Concurrent.STM import Control.Monad (unless, when) import Control.Monad.Except (runExceptT) import Data.ByteString (ByteString) -import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as B import Data.Char (isDigit) import Data.List (isPrefixOf, isSuffixOf) @@ -35,6 +34,7 @@ import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow, withTransaction) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff) +import qualified Simplex.Messaging.Encoding.Base64 as B64 import Simplex.Messaging.Encoding.String import Simplex.Messaging.Version import System.Directory (doesFileExist) diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index 77269a9b33..b6bc91e48b 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -75,7 +75,9 @@ skipComparisonForDownMigrations = -- on down migration idx_connections_via_contact_uri_hash index moves down to the end of the file "20231019_indexes", -- table and indexes move down to the end of the file - "20231215_recreate_msg_deliveries" + "20231215_recreate_msg_deliveries", + -- on down migration idx_msg_deliveries_agent_ack_cmd_id index moves down to the end of the file + "20240313_drop_agent_ack_cmd_id" ] getSchema :: FilePath -> FilePath -> IO String diff --git a/tests/WebRTCTests.hs b/tests/WebRTCTests.hs index a473afef36..f2372acc2f 100644 --- a/tests/WebRTCTests.hs +++ b/tests/WebRTCTests.hs @@ -4,12 +4,12 @@ module WebRTCTests where import Control.Monad.Except import Crypto.Random (getRandomBytes) -import qualified Data.ByteString.Base64.URL as U import qualified Data.ByteString.Char8 as B import Foreign.StablePtr import Simplex.Chat.Mobile import Simplex.Chat.Mobile.WebRTC import qualified Simplex.Messaging.Crypto as C +import qualified Simplex.Messaging.Encoding.Base64.URL as U import System.FilePath (()) import Test.Hspec @@ -36,8 +36,8 @@ webRTCTests = describe "WebRTC crypto" $ do cc <- newStablePtr c let key = B.replicate 32 '#' frame <- (<> B.replicate reservedSize '\NUL') <$> getRandomBytes 100 - runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0" - runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0" + runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "invalid key: invalid base64 encoding near offset: 0" + runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid base64 encoding near offset: 0" it "should fail on invalid auth tag" $ \tmp -> do Right c <- chatMigrateInit (tmp "1") "" "yesUp" cc <- newStablePtr c diff --git a/website/langs/fi.json b/website/langs/fi.json index 1d704cc8ec..0b82902fdc 100644 --- a/website/langs/fi.json +++ b/website/langs/fi.json @@ -28,7 +28,7 @@ "hero-overlay-2-title": "Miksi käyttäjätunnukset ovat huonoja yksityisyydelle?", "feature-1-title": "Päästä päähän salattuja viestejä markdownin ja muokkaamisen kera", "feature-2-title": "Päästä päähän salattuja
kuvia, videoita ja tiedostoja", - "feature-3-title": "Hajautetut salaiset ryhmät —
vain käyttäjät tietävät niiden olemassaolosta", + "feature-3-title": "E2E-salatut hajautetut ryhmät — vain käyttäjät tietävät niiden olemassaolosta", "feature-4-title": "Päästä päähän salattuja ääniviestejä", "feature-5-title": "Katoavia viestejä", "feature-8-title": "Incognito-tila —
ainutlaatuinen SimpleX Chatille", @@ -108,7 +108,7 @@ "hero-2-header": "Luo yksityinen yhteys", "hero-2-header-desc": "Video näyttää, kuinka muodostat yhteyden ystävääsi heidän kertakäyttöiseen QR-koodiinsa, henkilökohtaisesti tai videolinkin kautta. Voit myös liittyä jakamalla kutsulinkin kautta.", "feature-6-title": "Päästä päähän salattuja
puheluita ja videopuheluja", - "feature-7-title": "Siirrettävä salattu tietokanta — siirrä profiilisi toiselle laitteelle", + "feature-7-title": "Kannettava salattu sovellustallennus — siirrä profiili toiseen laitteeseen", "simplex-explained-tab-1-p-1": "Voit luoda yhteyshenkilöitä ja ryhmiä sekä käydä kaksisuuntaisia keskusteluja kuten missä tahansa muussa viestisovelluksessa.", "simplex-explained-tab-3-p-1": "Palvelimilla on erilliset anonyymit tunnistetiedot kullekin jonolle, eivätkä ne tiedä, mille käyttäjille ne kuuluvat.", "donate": "Lahjoita", @@ -245,5 +245,12 @@ "stable-and-beta-versions-built-by-developers": "Kehittäjien luomat vakaat ja beta-versiot", "f-droid-page-simplex-chat-repo-section-text": "Lisätäksesi sen F-Droid-asiakkaaseesi, skannaa QR-koodi tai käytä tätä URL-osoitetta:", "jobs": "Liity tiimiin", - "docs-dropdown-9": "Lataukset" + "docs-dropdown-9": "Lataukset", + "hero-overlay-3-title": "Turvallisuuden arviointi", + "hero-overlay-card-3-p-1": "Trail of Bits on johtava turvallisuus- ja teknologiakonsultointiyritys, jonka asiakkaita ovat muun muassa suuret teknologiayritykset, valtion virastot ja suuret lohkoketjuprojektit.", + "hero-overlay-3-textlink": "Turvallisuusarviointi", + "hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022.", + "hero-overlay-card-3-p-3": "Lue lisää ilmoituksesta.", + "please-enable-javascript": "Ota JavaScript käyttöön nähdäksesi QR-koodin.", + "please-use-link-in-mobile-app": "Käytä mobiilisovelluksessa olevaa linkkiä" } diff --git a/website/langs/he.json b/website/langs/he.json index f4e776c67f..9effb8474b 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -21,7 +21,7 @@ "hero-2-header-desc": "הסרטון מראה כיצד אתם יוצרים קשר עם חברכם באמצעות קוד QR חד פעמי, באופן אישי או באמצעות קישור וידאו. באפשרותכם גם להתחבר על-ידי שיתוף קישור ההזמנה.", "hero-overlay-1-title": "איך SimpleX עובד?", "feature-1-title": "הודעות מוצפנות מקצה לקצה עם סימונים ואפשרויות עריכה", - "feature-2-title": "תמונות וקבצים
מוצפנים מקצה לקצה", + "feature-2-title": "תמונות, וידאו וקבצים
בהצפנת קצה-אל-קצה", "feature-3-title": "קבוצות סודיות מבוזרות —
רק המשתמשים יודעים שהן קיימות", "simplex-private-3-title": "תעבורת TLS
מאובטחת ומאומתת", "simplex-private-card-1-point-2": "תיבת הצפנה NaCL בכל תור כדי למנוע קורלציית תעבורה בין תורי הודעות במקרה שאבטחת TLS נפגעה.", @@ -59,7 +59,7 @@ "feature-6-title": "שיחות שמע ווידאו
מוצפנות מקצה לקצה", "feature-4-title": "הודעות קוליות מוצפנות מקצה לקצה", "feature-5-title": "הודעות נעלמות", - "feature-7-title": "מסד נתונים מוצפן נייד — העברת הפרופיל שלכם למכשיר אחר", + "feature-7-title": "מסד נתונים מוצפן נייד — העבר פרופיל למכשיר אחר", "feature-8-title": "מצב זהות נסתרת —
ייחודי ל-SimpleX Chat", "simplex-private-4-title": "אופציונלי
גישה דרך Tor", "simplex-network-overlay-1-title": "השוואה לפרוטוקולי העברת הודעות P2P", @@ -250,5 +250,7 @@ "stable-and-beta-versions-built-by-developers": "גרסאות יציבות ובטא שנבנו על ידי המפתחים", "f-droid-page-simplex-chat-repo-section-text": "כדי להוסיף אותו ללקוח F-Droid שלכם, סרקו את קוד ה-QR או השתמשו בכתובת האתר הזו:", "docs-dropdown-8": "שירות מדריך כתובות SimpleX", - "f-droid-page-f-droid-org-repo-section-text": "מאגרי SimpleX Chat ו-F-Droid.org חותמים על גרסאות עם מפתחות שונים. כדי לעבור, אנא ייצא את מסד הנתונים של הצ'אט והתקן מחדש את האפליקציה." + "f-droid-page-f-droid-org-repo-section-text": "מאגרי SimpleX Chat ו-F-Droid.org חותמים על גרסאות עם מפתחות שונים. כדי לעבור, אנא ייצא את מסד הנתונים של הצ'אט והתקן מחדש את האפליקציה.", + "please-enable-javascript": "אנא הפעל JavaScript כדי לראות את קוד ה-QR.", + "please-use-link-in-mobile-app": "אנא השתמש בקישור באפליקציה במכשיר נייד" } diff --git a/website/langs/pl.json b/website/langs/pl.json index 041176a1e1..3e7c546cf1 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -194,7 +194,7 @@ "simplex-network-1-header": "W przeciwieństwie do sieci P2P", "simplex-network-section-desc": "Simplex Chat zapewnia najlepszą prywatność dzięki połączeniu zalet sieci P2P i sieci federacyjnych.", "simplex-network-2-header": "W przeciwieństwie do sieci federacyjnych", - "simplex-private-section-header": "Co sprawia, że SimpleX jestprywatny", + "simplex-private-section-header": "Co sprawia, że SimpleX jest prywatny", "tap-to-close": "Stuknij, aby zamknąć", "simplex-network-section-header": "SimpleX Sieć", "simplex-network-2-desc": "Serwery przekaźnikowe SimpleX NIE przechowują profili użytkowników, kontaktów i dostarczonych wiadomości, NIE łączą się ze sobą i NIE ma katalogu serwerów.", diff --git a/website/langs/ru.json b/website/langs/ru.json index 73fba95480..1629aafe54 100644 --- a/website/langs/ru.json +++ b/website/langs/ru.json @@ -250,5 +250,7 @@ "simplex-unique-overlay-card-1-p-1": "В отличие от других платформ обмена сообщениями, SimpleX не имеет идентификаторов, присвоенных пользователям. Он не полагается на номера телефонов, доменные адреса (например, электронную почту или XMPP), имена пользователей, открытые ключи или даже случайные числа для идентификации своих пользователей — мы не знаем, сколько людей пользуются нашими SimpleX серверами.", "reference": "Ссылки", "f-droid-page-f-droid-org-repo-section-text": "Приложение SimpleX Chat от разработчиков и от репозитория F-Droid.org имеют разные ключи подписи. Если вы хотите сменить одно на другое, вам сначала нужно будет экспортировать базу данных и только потом скачать другое приложение.", - "simplex-private-5-title": "Многоуровневое
Заполнения содержимого" + "simplex-private-5-title": "Многоуровневое
Заполнения содержимого", + "please-use-link-in-mobile-app": "Пожалуйста, воспользуйтесь ссылкой в мобильном приложении", + "please-enable-javascript": "Пожалуйста, включите JavaScript, чтобы увидеть QR-код." } diff --git a/website/src/_includes/blog_previews/20240323.html b/website/src/_includes/blog_previews/20240323.html new file mode 100644 index 0000000000..7fe4c78179 --- /dev/null +++ b/website/src/_includes/blog_previews/20240323.html @@ -0,0 +1,16 @@ +

SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance:

+ +
    +
  • community and business interests are aligned.
  • +
  • the journey to the decentralized non-profit protocol governance.
  • +
  • welcome, Esra'a!
  • +
+ +

v5.6 is released:

+ +
    +
  • quantum resistant end-to-end encryption (BETA) - enable it for the new contacts.
  • +
  • use the app during the audio and video calls.
  • +
  • migrate all app data to another device via QR code.
  • +
  • blocking members and many other improvements.
  • +