Compare commits

...

44 Commits

Author SHA1 Message Date
IC Rainbow ace687b7d3 revert disable pcre2 jit 2024-10-28 15:28:51 +02:00
IC Rainbow 59b5d07252 add mingw64 paths to windows cabal.project.local 2024-10-28 13:34:07 +02:00
IC Rainbow 82400aac66 disable pcre2 jit 2024-10-28 13:27:30 +02:00
IC Rainbow 7df7434a92 fix mingw package name 2024-10-28 12:48:36 +02:00
IC Rainbow f243f01bd1 fix test build 2024-10-27 12:34:11 +02:00
IC Rainbow 6607c24de3 add missing fields 2024-10-27 12:23:22 +02:00
IC Rainbow 4086238090 fix package name 2024-10-27 12:23:14 +02:00
IC Rainbow a1bbecc7ad Merge remote-tracking branch 'origin/master' into ab/rocksdb 2024-10-27 12:19:35 +02:00
spaced4ndy 78510b6fd3 core, ios: get messages for multiple last notifications; separately get notification connections before requesting messages (to avoid acknowledgement races in case of parralel nse threads); coordinate nse threads (#5084)
* core, ios: get messages for multiple last notifications (#5047)

* ios: refactor notification service (#5086)

* core, ios: separately get notification connections before requesting messages; coordinate nse threads (#5085)
2024-10-25 20:09:59 +04:00
IC Rainbow efa0d6d551 add rocksdb to CI 2024-10-24 14:41:07 +03:00
IC Rainbow 17214a55e9 WIP: test rocksdb build 2024-10-24 13:48:56 +03:00
Evgeny Poberezkin edf99fcd1d 6.1.1: ios 245, android 249, desktop 74 2024-10-18 18:37:14 +01:00
spaced4ndy 2ffabd1ef8 ios: fix changing user via notification (#5069) 2024-10-18 18:07:38 +04:00
Evgeny Poberezkin 28383edb83 core: 6.1.1.0 (simplexmq: 6.1.1.0) 2024-10-18 14:21:17 +01:00
Evgeny 9175897acf core, ui: add SMP STORE error (#5071)
* core, ui: add SMP STORE error

* update library
2024-10-18 14:17:04 +01:00
Evgeny Poberezkin f3cd167502 core: ntf server 2024-10-18 13:06:47 +01:00
Arturs Krumins 7cde2cf6c2 ios: optimise ComposeView rendering (#5042)
* ios: replace revealed bindings with constant value

* ios: optimise ComposeView rendering

* rename

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-10-18 12:35:27 +01:00
Arturs Krumins 3913043705 ios: fix chat not loading if initial page has too many merged items (#5066)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-10-18 10:07:18 +01:00
Arturs Krumins a160acef12 ios: fix navigation title redaction after biometric authentication (#5065) 2024-10-18 10:04:53 +01:00
Arturs Krumins c54fae0136 ios: fix sheets dismissing during biometric authentication (#5062)
* ios: fix sheets dismissing during biometric authentication

* remove AppSheet

* Revert "remove AppSheet"

This reverts commit 3aa1688cbd.

* remove local auth request on sheet dismissal

* revert biometricAuth
2024-10-16 19:55:59 +01:00
Arturs Krumins d57abfcc93 ios: fix theme import file picker (#5048)
* ios: fix theme import file picker

* minor
2024-10-16 19:48:13 +01:00
Evgeny 515a0ddfdd blog: wired's attack on privacy (#5063)
* blog: wired misleading attack on privacy of communications

* image

* update

* title

* update

* update

* preview
2024-10-16 19:25:47 +01:00
spaced4ndy b5d8c65249 ui: quota error description (#5037) 2024-10-15 12:01:06 +04:00
Arturs Krumins de94892fe7 ios: replace revealed bindings with constant value (#5027) 2024-10-15 08:58:54 +01:00
Evgeny Poberezkin b7131e16f2 docs: fix links 2024-10-14 13:27:04 +01:00
Evgeny 11a44dc1fd blog: v6.1 and security review announcement (#5040)
* blog: v6.1 and security review announcement

* update, images

* readme

* update review links on home page

* links to review
2024-10-14 13:18:48 +01:00
sh 0af718f03f flatpak: update metainfo (#5039) 2024-10-14 10:12:00 +01:00
Evgeny Poberezkin f8f5c3c6be docs: correction 2024-10-14 09:37:31 +01:00
sh 13912a4af9 docs/smp-server: update to latest changes (#4960)
* docs/smp-server: update to latest changes

* update

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-10-13 09:43:47 +01:00
Evgeny Poberezkin 601b4cd619 6.1: ios 244, android 247, desktop 73 2024-10-12 12:08:16 +01:00
Evgeny d2b4b7bed6 ui: translations (#5031)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Czech)

Currently translated at 96.1% (2008 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* process localizations

---------

Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: summoner001 <summoner@vivaldi.net>
2024-10-12 11:36:49 +01:00
Diogo fa95e4e9ad ios: dont show tails for moderated and blocked items unless revealed (#5030)
* ios: stop showing tails for non revealed moderated or blocked items

* simplify

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-10-12 10:59:51 +01:00
Evgeny Poberezkin 88fdc1ef75 core: 6.1.0.9 2024-10-12 10:56:24 +01:00
Evgeny 7ab6e44a6e directory service: list pending groups (#5029)
* directory service: list pending groups

* user commands to remove a group from directory and to set initial member role (TODO tests)

* tests
2024-10-12 10:33:45 +01:00
Evgeny Poberezkin 26986686ca ios: fix link previews to be enabled by default 2024-10-12 09:06:05 +01:00
spaced4ndy e76dc33cf0 core: associate new contact with all corresponding members on member contact re-creation (e.g. after it was merged to many members and then deleted) (#5028) 2024-10-11 20:47:54 +01:00
Evgeny aa2eafdacb blog: v6.1 announcement placeholder (#5004)
* blog: v6.1 announcement placeholder

* draft
2024-10-11 20:46:08 +01:00
Evgeny Poberezkin 2c3c97f5cc 6.1-beta.5: ios 243, android 246, desktop 72 2024-10-11 18:46:45 +01:00
Evgeny 83f42704ea ui: translations (#5026)
* Translated using Weblate (Italian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (German)

Currently translated at 99.7% (1839 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (German)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Japanese)

Currently translated at 65.1% (1201 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Russian)

Currently translated at 100.0% (2089 of 2089 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1843 of 1843 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* process localizations

---------

Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Abdullah Koyuncu <wisewebworks@outlook.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: acevif <acevif@gmail.com>
2024-10-11 17:08:32 +01:00
Stanislav Dmitrenko 7ff6ef09fe android: hide mic icon when mic is disabled (#5025) 2024-10-11 16:54:15 +01:00
Evgeny Poberezkin dbe4504f05 core: 6.1.0.8 (simplexmq: 6.1.0.7) 2024-10-11 15:44:13 +01:00
spaced4ndy 9a87f344b5 core: do not regenerate key when accepting connection to avoid invalidating invitation link on bad networks (#5018)
* core: prepare conn (plan)

* update

* group join

* comment

* comment

* wip

* Revert "wip"

This reverts commit 0849f43377.

* accept

* save contact_id, reuse contact

* refactor

* simplexmq

* set contactUsed

* support retrying join

* exclude prepared connections from API responses

* avoid race with events

* avoid race better

* fix UI

* update library

* tmp

* update

* display error details on ios cmd prohibited

* underscore instead of empty

* Update apps/ios/Shared/Model/SimpleXAPI.swift

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* test

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Diogo <diogofncunha@gmail.com>
2024-10-11 15:37:38 +01:00
Stanislav Dmitrenko 2127c7dcce android, desktop: don't stop audio track on Android in calls (#5024)
* android, desktop: don't stop audio track on Android in calls

There is a problem related to managing selected audio output device in
call. When microphone is disabled, WebView turns speaker on without
need. No way to prevent it was found yet. This is temporary workaround
that makes everything work except it makes microphone icon visible in
status bar (microphone is not used actually in that moment)

* enabled=false
2024-10-11 14:37:36 +01:00
Stanislav Dmitrenko e3528d3ffe android: re-apply custom language when webview appears (#5022)
* android: re-apply custom language when webview appears

There is a bug on Android related to including WebView. App language
changes to system language regardless of what was set before in
context's configuration. Re-set needed to be done twice: after creating
of WebView and after removing it from a view

* add link to bug

---------

Co-authored-by: Evgeny <evgeny@poberezkin.com>
2024-10-11 14:36:57 +01:00
126 changed files with 2937 additions and 817 deletions
+18 -1
View File
@@ -131,6 +131,10 @@ jobs:
echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
echo "" >> cabal.project.local
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> cabal.project.local
- name: Unix prepare cabal.project.local for Mac
if: matrix.os == 'macos-13'
@@ -145,14 +149,22 @@ jobs:
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
echo "" >> cabal.project.local
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> cabal.project.local
- name: Install AppImage dependencies
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
run: sudo apt install -y desktop-file-utils
- name: Install package dependencies
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
run: sudo apt install -y librocksdb-dev
- name: Install pkg-config for Mac
if: matrix.os == 'macos-latest' || matrix.os == 'macos-13'
run: brew install pkg-config
run: brew install pkg-config rocksdb
- name: Unix prepare cabal.project.local for Ubuntu
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
@@ -322,6 +334,7 @@ jobs:
git
perl
make
mingw-w64-x86_64-rocksdb
pacboy: >-
toolchain:p
cmake:p
@@ -341,6 +354,10 @@ jobs:
echo " flags: +openssl" >> cabal.project.local
echo " extra-include-dirs: $openssl_windows_style_path\include" >> cabal.project.local
echo " extra-lib-dirs: $openssl_windows_style_path" >> cabal.project.local
echo "" >> cabal.project.local
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
echo " extra-include-dirs: /mingw64/include" >> cabal.project.local
echo " extra-lib-dirs: /mingw64/lib" >> cabal.project.local
rm -rf dist-newstyle/src/direct-sq*
sed -i "s/, unix /--, unix /" simplex-chat.cabal
+4 -2
View File
@@ -25,7 +25,9 @@ While SimpleX Chat Ltd is not a communication service provider, and provide publ
We see users and data sovereignty, and device and provider portability as critically important properties for any communication system.
SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
The cryptographic review of SimpleX protocols design was done in July 2024 by Trail of Bits see [the announcement](/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md).
### Your information
@@ -172,4 +174,4 @@ You accept the Conditions of Use of Software and Infrastructure ("Conditions") b
**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd at any time by deleting our Applications from your devices and discontinuing use of our Infrastructure. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd.
Updated April 24, 2024
Updated October 14, 2024
+5 -9
View File
@@ -233,14 +233,12 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent and important updates:
[Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md)
[Aug 14, 2024. SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md)
[Jun 4, 2024. SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md)
[Apr 26, 2024. SimpleX network: legally binding transparency, v5.7 released with better calls and messages.](./blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md)
[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)
@@ -249,10 +247,6 @@ Recent and important updates:
[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md).
[Jul 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md).
[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md).
[Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md).
[Mar 1, 2023. SimpleX File Transfer Protocol send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md).
@@ -409,7 +403,9 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
[SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed, and had many breaking changes and improvements in v1.0.0.
The security audit was performed in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about) see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
The cryptographic review of SimpleX protocols was done in July 2024 by Trail of Bits see [the announcement](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md).
SimpleX Chat is still a relatively early stage platform (the mobile apps were released in March 2022), so you may discover some bugs and missing features. We would really appreciate if you let us know anything that needs to be fixed or improved.
+3 -1
View File
@@ -13,6 +13,7 @@ struct ContentView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
@ObservedObject var appSheetState = AppSheetState.shared
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@EnvironmentObject var sceneDelegate: SceneDelegate
@@ -250,7 +251,8 @@ struct ContentView: View {
private func mainView() -> some View {
ZStack(alignment: .top) {
ChatListView(activeUserPickerSheet: $chatListUserPickerSheet).privacySensitive(protectScreen)
ChatListView(activeUserPickerSheet: $chatListUserPickerSheet)
.redacted(reason: appSheetState.redactionReasons(protectScreen))
.onAppear {
requestNtfAuthorization()
// Local Authentication notice is to be shown on next start after onboarding is complete
+1
View File
@@ -147,6 +147,7 @@ final class ChatModel: ObservableObject {
@Published var chatDbEncrypted: Bool?
@Published var chatDbStatus: DBMigrationResult?
@Published var ctrlInitInProgress: Bool = false
@Published var notificationResponse: UNNotificationResponse?
// local authentication
@Published var contentViewAccessAuthenticated: Bool = false
@Published var laRequest: LocalAuthRequest?
+29 -10
View File
@@ -29,17 +29,33 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
private var granted = false
private var prevNtfTime: Dictionary<ChatId, Date> = [:]
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
// Handle notification when app is in background
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler handler: () -> Void) {
logger.debug("NtfManager.userNotificationCenter: didReceive")
let content = response.notification.request.content
if appStateGroupDefault.get() == .active {
processNotificationResponse(response)
} else {
logger.debug("NtfManager.userNotificationCenter: remember response in model")
ChatModel.shared.notificationResponse = response
}
handler()
}
func processNotificationResponse(_ ntfResponse: UNNotificationResponse) {
let chatModel = ChatModel.shared
let action = response.actionIdentifier
logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
let content = ntfResponse.notification.request.content
let action = ntfResponse.actionIdentifier
logger.debug("NtfManager.processNotificationResponse: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
if let userId = content.userInfo["userId"] as? Int64,
userId != chatModel.currentUser?.userId {
logger.debug("NtfManager.processNotificationResponse changeActiveUser")
changeActiveUser(userId, viewPwd: nil)
}
if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito),
@@ -61,7 +77,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
ItemsModel.shared.loadOpenChat(chatId)
}
}
handler()
}
private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? {
@@ -76,7 +91,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
return nil
}
// Handle notification when the app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
@@ -185,6 +199,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
actions: [],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: NSLocalizedString("SimpleX encrypted message or connection event", comment: "notification")
),
UNNotificationCategory(
identifier: ntfCategoryManyEvents,
actions: [],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: NSLocalizedString("New events", comment: "notification")
)
])
}
@@ -210,29 +230,28 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
}
}
}
center.delegate = self
}
func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) {
logger.debug("NtfManager.notifyContactRequest")
addNotification(createContactRequestNtf(user, contactRequest))
addNotification(createContactRequestNtf(user, contactRequest, 0))
}
func notifyContactConnected(_ user: any UserLike, _ contact: Contact) {
logger.debug("NtfManager.notifyContactConnected")
addNotification(createContactConnectedNtf(user, contact))
addNotification(createContactConnectedNtf(user, contact, 0))
}
func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) {
logger.debug("NtfManager.notifyMessageReceived")
if cInfo.ntfsEnabled {
addNotification(createMessageReceivedNtf(user, cInfo, cItem))
addNotification(createMessageReceivedNtf(user, cInfo, cItem, 0))
}
}
func notifyCallInvitation(_ invitation: RcvCallInvitation) {
logger.debug("NtfManager.notifyCallInvitation")
addNotification(createCallInvitationNtf(invitation))
addNotification(createCallInvitationNtf(invitation, 0))
}
func setNtfBadgeCount(_ count: Int) {
+7 -1
View File
@@ -462,7 +462,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String
let r = chatSendCmdSync(.apiGetNtfToken)
switch r {
case let .ntfToken(token, status, ntfMode, ntfServer): return (token, status, ntfMode, ntfServer)
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off, nil)
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED, _))): return (nil, nil, .off, nil)
default:
logger.debug("apiGetNtfToken response: \(String(describing: r))")
return (nil, nil, .off, nil)
@@ -743,6 +743,12 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi
message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection."
)
return (nil, alert)
case .chatCmdError(_, .errorAgent(.SMP(_, .QUOTA))):
let alert = mkAlert(
title: "Undelivered messages",
message: "The connection reached the limit of undelivered messages, your contact may be offline."
)
return (nil, alert)
case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))):
if internalErr == "SEUniqueID" {
let alert = mkAlert(
+11 -5
View File
@@ -82,11 +82,17 @@ struct SimpleXApp: App {
if appState != .stopped {
startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true {
Task {
await updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
if chatModel.chatRunning == true {
if let ntfResponse = chatModel.notificationResponse {
chatModel.notificationResponse = nil
NtfManager.shared.processNotificationResponse(ntfResponse)
}
if appState.inactive {
Task {
await updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
}
}
}
@@ -11,11 +11,11 @@ import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.revealed) var revealed: Bool
@ObservedObject var im = ItemsModel.shared
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
@Binding var revealed: Bool
var feature: Feature
var icon: String? = nil
var iconColor: Color
@@ -106,6 +106,9 @@ struct CIChatFeatureView: View {
struct CIChatFeatureView_Previews: PreviewProvider {
static var previews: some View {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(chat: Chat.sampleData, chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary))
CIChatFeatureView(
chat: Chat.sampleData,
chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary)
).environment(\.revealed, true)
}
}
@@ -285,17 +285,18 @@ struct CIFileView_Previews: PreviewProvider {
file: nil
)
Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentFile, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: sentFile)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample())
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile)
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))
}
}
@@ -510,10 +510,10 @@ struct CIVoiceView_Previews: PreviewProvider {
duration: 30,
allowMenu: Binding.constant(true)
)
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, allowMenu: .constant(true))
}
.previewLayout(.fixed(width: 360, height: 360))
}
@@ -92,12 +92,13 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
file: CIFile.getSample(fileStatus: .sndComplete)
)
Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote)
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))
}
}
File diff suppressed because one or more lines are too long
@@ -12,9 +12,9 @@ import SimpleXChat
struct MarkedDeletedItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
var body: some View {
(Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText)
@@ -79,7 +79,10 @@ struct MarkedDeletedItemView: View {
struct MarkedDeletedItemView_Previews: PreviewProvider {
static var previews: some View {
Group {
MarkedDeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true))
MarkedDeletedItemView(
chat: Chat.sampleData,
chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))
).environment(\.revealed, true)
}
.previewLayout(.fixed(width: 360, height: 200))
}
+31 -28
View File
@@ -14,19 +14,28 @@ extension EnvironmentValues {
static let defaultValue: Bool = true
}
struct Revealed: EnvironmentKey {
static let defaultValue: Bool = true
}
var showTimestamp: Bool {
get { self[ShowTimestamp.self] }
set { self[ShowTimestamp.self] = newValue }
}
var revealed: Bool {
get { self[Revealed.self] }
set { self[Revealed.self] = newValue }
}
}
struct ChatItemView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
@Environment(\.showTimestamp) var showTimestamp: Bool
@Environment(\.revealed) var revealed: Bool
var chatItem: ChatItem
var maxWidth: CGFloat = .infinity
@Binding var revealed: Bool
@Binding var allowMenu: Bool
init(
@@ -34,27 +43,25 @@ struct ChatItemView: View {
chatItem: ChatItem,
showMember: Bool = false,
maxWidth: CGFloat = .infinity,
revealed: Binding<Bool>,
allowMenu: Binding<Bool> = .constant(false)
) {
self.chat = chat
self.chatItem = chatItem
self.maxWidth = maxWidth
_revealed = revealed
_allowMenu = allowMenu
}
var body: some View {
let ci = chatItem
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed)
MarkedDeletedItemView(chat: chat, chatItem: chatItem)
} else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
EmojiItemView(chat: chat, chatItem: ci)
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu)
} else if ci.content.msgContent == nil {
ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
} else {
framedItemView()
}
@@ -84,7 +91,6 @@ struct ChatItemView: View {
chat: chat,
chatItem: chatItem,
preview: preview,
revealed: $revealed,
maxWidth: maxWidth,
imgWidth: adjustedMaxWidth,
videoWidth: adjustedMaxWidth,
@@ -96,9 +102,9 @@ struct ChatItemView: View {
struct ChatItemContentView<Content: View>: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
var msgContentView: () -> Content
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@@ -130,7 +136,7 @@ struct ChatItemContentView<Content: View>: View {
case let .rcvChatPreference(feature, allowed, param):
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
case let .sndChatPreference(feature, _, _):
CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
@@ -177,7 +183,7 @@ struct ChatItemContentView<Content: View>: View {
}
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor)
CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor)
}
private var mergedGroupEventText: Text? {
@@ -238,16 +244,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text {
struct ChatItemView_Previews: PreviewProvider {
static var previews: some View {
Group{
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true)).environment(\.revealed, true)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true)).environment(\.revealed, true)
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 70))
.environmentObject(Chat.sampleData)
}
@@ -265,8 +272,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)),
quotedItem: nil,
file: nil
),
revealed: Binding.constant(true)
)
)
ChatItemView(
chat: Chat.sampleData,
@@ -276,8 +282,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvDecryptionError(msgDecryptError: .ratchetHeader, msgCount: 2),
quotedItem: nil,
file: nil
),
revealed: Binding.constant(true)
)
)
ChatItemView(
chat: Chat.sampleData,
@@ -287,8 +292,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin),
quotedItem: nil,
file: nil
),
revealed: Binding.constant(true)
)
)
ChatItemView(
chat: Chat.sampleData,
@@ -298,8 +302,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)),
quotedItem: nil,
file: nil
),
revealed: Binding.constant(true)
)
)
ChatItemView(
chat: Chat.sampleData,
@@ -309,10 +312,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: ciFeatureContent,
quotedItem: nil,
file: nil
),
revealed: Binding.constant(true)
)
)
}
.environment(\.revealed, true)
.previewLayout(.fixed(width: 360, height: 70))
.environmentObject(Chat.sampleData)
}
+2 -2
View File
@@ -1183,11 +1183,11 @@ struct ChatView: View {
chat: chat,
chatItem: ci,
maxWidth: maxWidth,
revealed: .constant(revealed),
allowMenu: $allowMenu
)
.environment(\.revealed, revealed)
.environment(\.showTimestamp, itemSeparation.timestamp)
.modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap))
.modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed)))
.contextMenu { menu(ci, range, live: composeState.liveMessage != nil) }
.accessibilityLabel("")
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
@@ -287,8 +287,8 @@ struct ComposeView: View {
// this is a workaround to fire an explicit event in certain cases
@State private var stopPlayback: Bool = false
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
@UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
var body: some View {
VStack(spacing: 0) {
@@ -40,7 +40,7 @@ struct SendMessageView: View {
@State private var showCustomTimePicker = false
@State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get()
@State private var progressByTimeout = false
@AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false
@UserDefault(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false
var body: some View {
ZStack {
+1 -1
View File
@@ -78,7 +78,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
tableView: tableView
) { (tableView, indexPath, item) -> UITableViewCell? in
if indexPath.item > self.itemCount - 8, self.itemCount > 8 {
if indexPath.item > self.itemCount - 8 {
self.representer.loadPage()
}
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
@@ -121,9 +121,11 @@ struct ChatListView: View {
UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet)
}
)
.sheet(item: $activeUserPickerSheet) {
UserPickerSheetView(sheet: $0)
}
.appSheet(
item: $activeUserPickerSheet,
onDismiss: { chatModel.laRequest = nil },
content: { UserPickerSheetView(sheet: $0) }
)
.onChange(of: activeUserPickerSheet) {
if $0 != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+7 -5
View File
@@ -11,6 +11,12 @@ import SwiftUI
class AppSheetState: ObservableObject {
static let shared = AppSheetState()
@Published var scenePhaseActive: Bool = false
func redactionReasons(_ protectScreen: Bool) -> RedactionReasons {
!protectScreen || scenePhaseActive
? RedactionReasons()
: RedactionReasons.placeholder
}
}
private struct PrivacySensitive: ViewModifier {
@@ -19,11 +25,7 @@ private struct PrivacySensitive: ViewModifier {
@ObservedObject var appSheetState: AppSheetState = AppSheetState.shared
func body(content: Content) -> some View {
if !protectScreen {
content
} else {
content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy)
}
content.redacted(reason: appSheetState.redactionReasons(protectScreen))
}
}
@@ -36,12 +36,7 @@ struct ChatItemClipped: ViewModifier {
.sndMsgContent,
.rcvMsgContent,
.rcvDecryptionError,
.sndDeleted,
.rcvDeleted,
.rcvIntegrityError,
.sndModerated,
.rcvModerated,
.rcvBlocked,
.invalidJSON:
let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty {
false
@@ -16,6 +16,7 @@ struct UserWallpaperEditor: View {
@State var themeModeOverride: ThemeModeOverride
@State var applyToMode: DefaultThemeMode?
@State var showMore: Bool = false
@State var showFileImporter: Bool = false
@Binding var globalThemeUsed: Bool
var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void
@@ -125,24 +126,27 @@ struct UserWallpaperEditor: View {
CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) })
ImportExportThemeSection(perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in
let importedFromString = imported.wallpaper?.importFromString()
let importedType = importedFromString?.toAppWallpaper().type
let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get())
let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType }
let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors
let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors)
Task {
await MainActor.run {
themeModeOverride = res
}
await save(applyToMode, res)
}
}
ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes)
} else {
AdvancedSettingsButton(theme.colors.primary) { showMore = true }
}
}
.modifier(
ThemeImporter(isPresented: $showFileImporter) { imported in
let importedFromString = imported.wallpaper?.importFromString()
let importedType = importedFromString?.toAppWallpaper().type
let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get())
let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType }
let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors
let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors)
Task {
await MainActor.run {
themeModeOverride = res
}
await save(applyToMode, res)
}
}
)
}
private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool {
@@ -216,6 +220,7 @@ struct ChatWallpaperEditor: View {
@State var themeModeOverride: ThemeModeOverride
@State var applyToMode: DefaultThemeMode?
@State var showMore: Bool = false
@State var showFileImporter: Bool = false
@Binding var globalThemeUsed: Bool
var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void
@@ -328,24 +333,27 @@ struct ChatWallpaperEditor: View {
CustomizeThemeColorsSection(editColor: editColor)
ImportExportThemeSection(perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in
let importedFromString = imported.wallpaper?.importFromString()
let importedType = importedFromString?.toAppWallpaper().type
let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType }
let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors
let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors)
Task {
await MainActor.run {
themeModeOverride = res
}
await save(applyToMode, res)
}
}
ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes)
} else {
AdvancedSettingsButton(theme.colors.primary) { showMore = true }
}
}
.modifier(
ThemeImporter(isPresented: $showFileImporter) { imported in
let importedFromString = imported.wallpaper?.importFromString()
let importedType = importedFromString?.toAppWallpaper().type
let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType }
let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors
let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors)
Task {
await MainActor.run {
themeModeOverride = res
}
await save(applyToMode, res)
}
}
)
}
private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool {
@@ -0,0 +1,62 @@
//
// UserDefault.swift
// SimpleX (iOS)
//
// Created by user on 14/10/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import Combine
@propertyWrapper
public struct UserDefault<Value: Equatable>: DynamicProperty {
@StateObject private var observer = UserDefaultObserver()
let initialValue: Value
let key: String
let store: UserDefaults
public init(
wrappedValue: Value,
_ key: String,
store: UserDefaults = .standard
) {
self.initialValue = wrappedValue
self.key = key
self.store = store
}
public var wrappedValue: Value {
get {
// Observer can only be accessed after the property wrapper is installed in view (runtime exception)
observer.subscribe(to: key)
return store.object(forKey: key) as? Value ?? initialValue
}
nonmutating set {
store.set(newValue, forKey: key)
}
}
}
private class UserDefaultObserver: ObservableObject {
private var subscribed = false
func subscribe(to key: String) {
if !subscribed {
NotificationCenter.default.addObserver(
self,
selector: #selector(userDefaultsDidChange),
name: UserDefaults.didChangeNotification,
object: nil
)
subscribed = true
}
}
@objc
private func userDefaultsDidChange(_ notification: Notification) {
Task { @MainActor in objectWillChange.send() }
}
deinit { NotificationCenter.default.removeObserver(self) }
}
+6 -1
View File
@@ -105,7 +105,12 @@ struct TerminalView: View {
}
}
.navigationViewStyle(.stack)
.navigationTitle("Chat console")
.toolbar {
// Redaction broken for `.navigationTitle` - using a toolbar item instead.
ToolbarItem(placement: .principal) {
Text("Chat console").font(.headline)
}
}
.modifier(ThemedBackground())
}
@@ -367,13 +367,13 @@ struct ChatThemePreview: View {
let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview"))
let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir))
HStack {
ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: alice)
.modifier(ChatItemClipped(alice, tailVisible: true))
Spacer()
}
HStack {
Spacer()
ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: bob)
.modifier(ChatItemClipped(bob, tailVisible: true))
.frame(alignment: .trailing)
}
@@ -583,11 +583,14 @@ struct CustomizeThemeView: View {
}
}
ImportExportThemeSection(perChat: nil, perUser: nil, save: { theme in
ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: nil)
}
.modifier(
ThemeImporter(isPresented: $showFileImporter) { theme in
ThemeManager.saveAndApplyThemeOverrides(theme)
saveThemeToDatabase(nil)
})
}
}
)
/// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker()
.interactiveDismissDisabled(true)
}
@@ -595,10 +598,9 @@ struct CustomizeThemeView: View {
struct ImportExportThemeSection: View {
@EnvironmentObject var theme: AppTheme
@Binding var showFileImporter: Bool
var perChat: ThemeModeOverride?
var perUser: ThemeModeOverrides?
var save: (ThemeOverrides) -> Void
@State private var showFileImporter = false
var body: some View {
Section {
@@ -626,39 +628,47 @@ struct ImportExportThemeSection: View {
} label: {
Text("Import theme").foregroundColor(theme.colors.primary)
}
.fileImporter(
isPresented: $showFileImporter,
allowedContentTypes: [.data/*.plainText*/],
allowsMultipleSelection: false
) { result in
if case let .success(files) = result, let fileURL = files.first {
do {
var fileSize: Int? = nil
if fileURL.startAccessingSecurityScopedResource() {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
fileSize = resourceValues.fileSize
}
if let fileSize = fileSize,
// Same as Android/desktop
fileSize <= 5_500_000 {
if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) {
save(theme)
logger.error("Saved theme from file")
} else {
logger.error("Error decoding theme file")
}
fileURL.stopAccessingSecurityScopedResource()
} else {
fileURL.stopAccessingSecurityScopedResource()
let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary)
AlertManager.shared.showAlertMsg(
title: "Large file!",
message: "Currently maximum supported file size is \(prettyMaxFileSize)."
)
}
} catch {
logger.error("Appearance fileImporter error \(error.localizedDescription)")
}
}
}
struct ThemeImporter: ViewModifier {
@Binding var isPresented: Bool
var save: (ThemeOverrides) -> Void
func body(content: Content) -> some View {
content.fileImporter(
isPresented: $isPresented,
allowedContentTypes: [.data/*.plainText*/],
allowsMultipleSelection: false
) { result in
if case let .success(files) = result, let fileURL = files.first {
do {
var fileSize: Int? = nil
if fileURL.startAccessingSecurityScopedResource() {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
fileSize = resourceValues.fileSize
}
if let fileSize = fileSize,
// Same as Android/desktop
fileSize <= 5_500_000 {
if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) {
save(theme)
logger.error("Saved theme from file")
} else {
logger.error("Error decoding theme file")
}
fileURL.stopAccessingSecurityScopedResource()
} else {
fileURL.stopAccessingSecurityScopedResource()
let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary)
AlertManager.shared.showAlertMsg(
title: "Large file!",
message: "Currently maximum supported file size is \(prettyMaxFileSize)."
)
}
} catch {
logger.error("Appearance fileImporter error \(error.localizedDescription)")
}
}
}
@@ -331,7 +331,12 @@ struct SettingsView: View {
chatDatabaseRow()
NavigationLink {
MigrateFromDevice(showProgressOnSettings: $showProgress)
.navigationTitle("Migrate device")
.toolbar {
// Redaction broken for `.navigationTitle` - using a toolbar item instead.
ToolbarItem(placement: .principal) {
Text("Migrate device").font(.headline)
}
}
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
} label: {
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Verbesserte Anrufe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Verbesserte Nachrichten-Datumsinformation</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Verbesserte Benachrichtigungen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Verbesserte Sicherheit ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Verbesserte Nutzer-Erfahrung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1942,6 +1947,7 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Anpassbares Format des Nachrichtenfelds</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2240,6 +2246,7 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Bis zu 200 Nachrichten löschen oder moderieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3367,6 +3374,7 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Bis zu 20 Nachrichten auf einmal weiterleiten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Fehler: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch.
Weitere Verbesserungen sind bald verfügbar!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>Die SimpleX-Protokolle wurden von Trail of Bits überprüft.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Während des Anrufs zwischen Audio und Video wechseln</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Das Chat-Profil für Einmal-Einladungen wechseln</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Llamadas mejoradas</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Sistema de fechas mejorado.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Notificaciones mejoradas</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Seguridad mejorada ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Experiencia de usuario mejorada</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1942,6 +1947,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Forma personalizable de los mensajes.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2240,6 +2246,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Borra o modera hasta 200 mensajes a la vez.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3367,6 +3374,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Desplazamiento de hasta 20 mensajes.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Error: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Reducción del tráfico y entrega mejorada.
¡Pronto habrá nuevas mejoras!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Actívalo en ajustes de *Servidores y Redes*.</target>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>Protocolos de SimpleX auditados por Trail of Bits.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Actívalo en ajustes de *Servidores y Redes*.</target>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Intercambia audio y video durante la llamada.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Cambia el perfil de chat para invitaciones de un solo uso.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -762,7 +762,7 @@
</trans-unit>
<trans-unit id="All your contacts will remain connected. Profile update will be sent to your contacts." xml:space="preserve">
<source>All your contacts will remain connected. Profile update will be sent to your contacts.</source>
<target>Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.</target>
<target>Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve">
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Továbbfejlesztett hívásélmény</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Továbbfejlesztett üzenetdátumok.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Továbbfejlesztett értesítések</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Továbbfejlesztett biztonság ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Továbbfejlesztett felhasználói élmény</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1408,7 +1413,7 @@
</trans-unit>
<trans-unit id="Chat profile" xml:space="preserve">
<source>Chat profile</source>
<target>Felhasználói profil</target>
<target>Csevegési profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat theme" xml:space="preserve">
@@ -1433,7 +1438,7 @@
</trans-unit>
<trans-unit id="Choose _Migrate from another device_ on the new device and scan QR code." xml:space="preserve">
<source>Choose _Migrate from another device_ on the new device and scan QR code.</source>
<target>Válassza az _Átköltöztetés egy másik eszközről opciót az új eszközén és olvassa be a QR-kódot.</target>
<target>Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Choose file" xml:space="preserve">
@@ -1717,7 +1722,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
<source>Connection timeout</source>
<target>Kapcsolat időtúllépés</target>
<target>Időtúllépés kapcsolódáskor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection with desktop stopped" xml:space="preserve">
@@ -1942,6 +1947,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Testreszabható üzenetbuborékok.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -1971,7 +1977,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Database IDs and Transport isolation option." xml:space="preserve">
<source>Database IDs and Transport isolation option.</source>
<target>Adatbázis-azonosítók és átviteli izolációs beállítások.</target>
<target>Adatbázis-azonosítók és átvitel-izolációs beállítások.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade" xml:space="preserve">
@@ -2240,6 +2246,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -2349,7 +2356,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Details" xml:space="preserve">
<source>Details</source>
<target>Részletek</target>
<target>További részletek</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -2384,7 +2391,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Different names, avatars and transport isolation." xml:space="preserve">
<source>Different names, avatars and transport isolation.</source>
<target>Különböző nevek, avatarok és átviteli izoláció.</target>
<target>Különböző nevek, profilképek és átvitel-izoláció.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Direct messages" xml:space="preserve">
@@ -2910,7 +2917,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Error deleting user profile" xml:space="preserve">
<source>Error deleting user profile</source>
<target>Hiba a felhasználói profil törlésekor</target>
<target>Hiba a felhasználó-profil törlésekor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error downloading the archive" xml:space="preserve">
@@ -3025,7 +3032,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Error saving user password" xml:space="preserve">
<source>Error saving user password</source>
<target>Hiba a felhasználó jelszavának mentésekor</target>
<target>Hiba a felhasználói jelszó mentésekor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error scanning code: %@" xml:space="preserve">
@@ -3095,7 +3102,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Error updating user privacy" xml:space="preserve">
<source>Error updating user privacy</source>
<target>Hiba a felhasználói beállítások frissítésekor</target>
<target>Hiba a felhasználói adatvédelem frissítésekor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error uploading the archive" xml:space="preserve">
@@ -3367,6 +3374,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Legfeljebb 20 üzenet egyszerre való továbbítása.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3615,7 +3623,7 @@ Hiba: %2$@</target>
</trans-unit>
<trans-unit id="Hide" xml:space="preserve">
<source>Hide</source>
<target>Elrejtés</target>
<target>Összecsukás</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
@@ -3700,7 +3708,7 @@ Hiba: %2$@</target>
</trans-unit>
<trans-unit id="If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." xml:space="preserve">
<source>If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app).</source>
<target>Ha most kell használnia a csevegést, koppintson alább az **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése).</target>
<target>Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ignore" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Hiba: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat.
További fejlesztések hamarosan!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -4205,7 +4215,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz!</target>
</trans-unit>
<trans-unit id="Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" xml:space="preserve">
<source>Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*</source>
<target>Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*</target>
<target>Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Mark deleted for everyone" xml:space="preserve">
@@ -4783,7 +4793,7 @@ VPN engedélyezése szükséges.</target>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<target>Csak az eszközök alkalmazásai tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket.</target>
<target>Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only delete conversation" xml:space="preserve">
@@ -4893,7 +4903,7 @@ VPN engedélyezése szükséges.</target>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Felhasználói profilok megnyitása</target>
<target>Felhasználó-profilok megnyitása</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
@@ -4945,12 +4955,12 @@ VPN engedélyezése szükséges.</target>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>PING számláló</target>
<target>PING-ek száma</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING interval" xml:space="preserve">
<source>PING interval</source>
<target>PING időköze</target>
<target>Időtartam a PING-ek között</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passcode" xml:space="preserve">
@@ -5306,12 +5316,12 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Protocol timeout" xml:space="preserve">
<source>Protocol timeout</source>
<target>Protokoll időtúllépés</target>
<target>Protokoll időtúllépése</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protocol timeout per KB" xml:space="preserve">
<source>Protocol timeout per KB</source>
<target>Protokoll időkorlát KB-onként</target>
<target>Protokoll időtúllépése KB-onként</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Proxied" xml:space="preserve">
@@ -5436,7 +5446,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Received total" xml:space="preserve">
<source>Received total</source>
<target>Összes fogadott</target>
<target>Összes fogadott üzenet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Receiving address will be changed to a different server. Address change will complete after sender comes online." xml:space="preserve">
@@ -5808,7 +5818,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Save profile password" xml:space="preserve">
<source>Save profile password</source>
<target>Felhasználói fiók jelszavának mentése</target>
<target>Profiljelszó mentése</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save servers" xml:space="preserve">
@@ -6148,7 +6158,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Sent total" xml:space="preserve">
<source>Sent total</source>
<target>Összes elküldött</target>
<target>Összes elküldött üzenet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sent via proxy" xml:space="preserve">
@@ -6363,7 +6373,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Show message status" xml:space="preserve">
<source>Show message status</source>
<target>Üzenet állapot megjelenítése</target>
<target>Üzenetállapot megjelenítése</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show percentage" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Hang/Videó váltása hívás közben.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Csevegési profilváltás az egyszer használható meghívókhoz.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -6665,7 +6678,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>TCP kapcsolat időtúllépés</target>
<target>TCP kapcsolat időtúllépése</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP_KEEPCNT" xml:space="preserve">
@@ -6700,7 +6713,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<target>Koppintson a kapcsolódáshoz</target>
<target>Koppintson ide a kapcsolódáshoz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
@@ -6710,12 +6723,12 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Tap to join" xml:space="preserve">
<source>Tap to join</source>
<target>Koppintson a csatlakozáshoz</target>
<target>Koppintson ide a csatlakozáshoz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to join incognito" xml:space="preserve">
<source>Tap to join incognito</source>
<target>Koppintson az inkognitóban való csatlakozáshoz</target>
<target>Koppintson ide az inkognitóban való csatlakozáshoz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to paste link" xml:space="preserve">
@@ -6725,7 +6738,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="Tap to scan" xml:space="preserve">
<source>Tap to scan</source>
<target>Koppintson a beolvasáshoz</target>
<target>Koppintson ide a QR-kód beolvasáshoz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Temporary file error" xml:space="preserve">
@@ -6770,7 +6783,7 @@ Engedélyezze a „Beállítások -&gt; Hálózat és kiszolgálók” menüben.
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<target>Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre.</target>
<target>Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The ID of the next message is incorrect (less or equal to the previous).&#10;It can happen because of some bug or when the connection is compromised." xml:space="preserve">
@@ -6992,7 +7005,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<target>Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.</target>
<target>Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -7059,12 +7072,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
</trans-unit>
<trans-unit id="Total" xml:space="preserve">
<source>Total</source>
<target>Összesen</target>
<target>Összes kapcsolat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Kapcsolat izolációs mód</target>
<target>Átvitel-izoláció módja</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport sessions" xml:space="preserve">
@@ -7728,7 +7741,7 @@ Csatlakozáskérés megismétlése?</target>
</trans-unit>
<trans-unit id="You can enable them later via app Privacy &amp; Security settings." xml:space="preserve">
<source>You can enable them later via app Privacy &amp; Security settings.</source>
<target>Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben.</target>
<target>Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can give another try." xml:space="preserve">
@@ -7738,7 +7751,7 @@ Csatlakozáskérés megismétlése?</target>
</trans-unit>
<trans-unit id="You can hide or mute a user profile - swipe it to the right." xml:space="preserve">
<source>You can hide or mute a user profile - swipe it to the right.</source>
<target>Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt.</target>
<target>Elrejtheti vagy lenémíthatja a felhasználó -profiljait - csúsztassa jobbra a profilt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
@@ -9068,7 +9081,7 @@ utoljára fogadott üzenet: %2$@</target>
</trans-unit>
<trans-unit id="NSCameraUsageDescription" xml:space="preserve">
<source>SimpleX needs camera access to scan QR codes to connect to other users and for video calls.</source>
<target>A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.</target>
<target>A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.</target>
<note>Privacy - Camera Usage Description</note>
</trans-unit>
<trans-unit id="NSFaceIDUsageDescription" xml:space="preserve">
@@ -9083,12 +9096,12 @@ utoljára fogadott üzenet: %2$@</target>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
<target>A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.</target>
<target>A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.</target>
<note>Privacy - Microphone Usage Description</note>
</trans-unit>
<trans-unit id="NSPhotoLibraryAddUsageDescription" xml:space="preserve">
<source>SimpleX needs access to Photo Library for saving captured and received media</source>
<target>A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez</target>
<target>A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez</target>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body>
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Chiamate migliorate</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Date dei messaggi migliorate.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Notifiche migliorate</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Sicurezza migliorata ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Esperienza utente migliorata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1942,6 +1947,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Forma dei messaggi personalizzabile.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2240,6 +2246,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Elimina o modera fino a 200 messaggi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3367,6 +3374,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Inoltra fino a 20 messaggi alla volta.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Errore: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Consegna migliorata, utilizzo di traffico ridotto.
Altri miglioramenti sono in arrivo!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>Protocolli di SimpleX esaminati da Trail of Bits.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Attivalo nelle impostazioni *Rete e server*.</target>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Cambia tra audio e video durante la chiamata.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Cambia profilo di chat per inviti una tantum.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -4121,10 +4121,12 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
<source>Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
<target>メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**エンドツーエンドの暗号化**によって保護されます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
<target>メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrate device" xml:space="preserve">
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Betere gesprekken</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Betere datums voor berichten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Betere meldingen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Betere beveiliging ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Betere gebruikerservaring</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1942,6 +1947,7 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Aanpasbare berichtvorm.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2240,6 +2246,7 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Maximaal 200 berichten verwijderen of modereren.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3367,6 +3374,7 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Stuur maximaal 20 berichten tegelijk door.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Fout: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Verbeterde levering, minder data gebruik.
Binnenkort meer verbeteringen!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Schakel dit in in *Netwerk en servers*-instellingen.</target>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>SimpleX-protocollen beoordeeld door Trail of Bits.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Schakel dit in in *Netwerk en servers*-instellingen.</target>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Wisselen tussen audio en video tijdens het gesprek.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Wijzig chatprofiel voor eenmalige uitnodigingen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -139,6 +139,7 @@
</trans-unit>
<trans-unit id="%@, %@" xml:space="preserve">
<source>%1$@, %2$@</source>
<target>%1$@, %2$@</target>
<note>format for date separator in chat</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
@@ -1086,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Улучшенные звонки</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1095,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Улучшенные даты сообщений.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1109,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Улучшенные уведомления</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Улучшенная безопасность ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Улучшенный интерфейс</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1941,6 +1947,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Настраиваемая форма сообщений.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2239,6 +2246,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>Удаляйте или модерируйте до 200 сообщений.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3366,6 +3374,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Пересылайте до 20 сообщений за раз.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3760,6 +3769,7 @@ Error: %2$@</source>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>Улучшенная доставка, меньше трафик.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6462,6 +6472,7 @@ Enable in *Network &amp; servers* settings.</source>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>Аудит SimpleX протоколов от Trail of Bits.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6641,10 +6652,12 @@ Enable in *Network &amp; servers* settings.</source>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Переключайте звук и видео во время звонка.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Переключайте профиль чата для одноразовых приглашений.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
@@ -1087,6 +1087,7 @@
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve">
<source>Better calls</source>
<target>Daha iyi aramalar</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
@@ -1096,6 +1097,7 @@
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve">
<source>Better message dates.</source>
<target>Daha iyi mesaj tarihleri.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
@@ -1110,14 +1112,17 @@
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve">
<source>Better notifications</source>
<target>Daha iyi bildirimler</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve">
<source>Better security ✅</source>
<target>Daha iyi güvenlik ✅</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve">
<source>Better user experience</source>
<target>Daha iyi kullanıcı deneyimi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Black" xml:space="preserve">
@@ -1942,6 +1947,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
</trans-unit>
<trans-unit id="Customizable message shape." xml:space="preserve">
<source>Customizable message shape.</source>
<target>Özelleştirilebilir mesaj şekli.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Customize theme" xml:space="preserve">
@@ -2240,6 +2246,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
</trans-unit>
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
<source>Delete or moderate up to 200 messages.</source>
<target>200'e kadar mesajı silin veya düzenleyin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
@@ -3367,6 +3374,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
</trans-unit>
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
<source>Forward up to 20 messages at once.</source>
<target>Aynı anda en fazla 20 mesaj iletin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve">
@@ -3761,6 +3769,8 @@ Hata: %2$@</target>
<trans-unit id="Improved delivery, reduced traffic usage.&#10;More improvements are coming soon!" xml:space="preserve">
<source>Improved delivery, reduced traffic usage.
More improvements are coming soon!</source>
<target>İyileştirilmiş teslimat, azaltılmış trafik kullanımı.
Daha fazla iyileştirme yakında geliyor!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Improved message delivery" xml:space="preserve">
@@ -6463,6 +6473,7 @@ Enable in *Network &amp; servers* settings.</source>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>SimpleX protokolleri Trail of Bits tarafından incelenmiştir.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@@ -6642,10 +6653,12 @@ Enable in *Network &amp; servers* settings.</source>
</trans-unit>
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
<source>Switch audio and video during the call.</source>
<target>Görüşme sırasında ses ve görüntüyü değiştirin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
<source>Switch chat profile for 1-time invitations.</source>
<target>Sohbet profilini 1 kerelik davetler için değiştirin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="System" xml:space="preserve">
+358 -138
View File
@@ -26,14 +26,52 @@ enum NSENotification {
case nse(UNMutableNotificationContent)
case callkit(RcvCallInvitation)
case empty
case msgInfo(NtfMsgAckInfo)
}
var isCallInvitation: Bool {
public enum NSENotificationData {
case connectionEvent(_ user: User, _ connEntity: ConnectionEntity)
case contactConnected(_ user: any UserLike, _ contact: Contact)
case contactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest)
case messageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem)
case callInvitation(_ invitation: RcvCallInvitation)
case msgInfo(NtfMsgAckInfo)
case noNtf
var callInvitation: RcvCallInvitation? {
switch self {
case let .nse(ntf): ntf.categoryIdentifier == ntfCategoryCallInvitation
case .callkit: true
case .empty: false
case .msgInfo: false
case let .callInvitation(invitation): invitation
default: nil
}
}
func notificationContent(_ badgeCount: Int) -> UNMutableNotificationContent {
return switch self {
case let .connectionEvent(user, connEntity): createConnectionEventNtf(user, connEntity, badgeCount)
case let .contactConnected(user, contact): createContactConnectedNtf(user, contact, badgeCount)
case let .contactRequest(user, contactRequest): createContactRequestNtf(user, contactRequest, badgeCount)
case let .messageReceived(user, cInfo, cItem): createMessageReceivedNtf(user, cInfo, cItem, badgeCount)
case let .callInvitation(invitation): createCallInvitationNtf(invitation, badgeCount)
case .msgInfo: UNMutableNotificationContent()
case .noNtf: UNMutableNotificationContent()
}
}
var notificationEvent: NSENotificationData? {
return switch self {
case .connectionEvent: self
case .contactConnected: self
case .contactRequest: self
case .messageReceived: self
case .callInvitation: self
case .msgInfo: nil
case .noNtf: nil
}
}
var newMsgData: (any UserLike, ChatInfo)? {
return switch self {
case let .messageReceived(user, cInfo, _): (user, cInfo)
default: nil
}
}
}
@@ -43,9 +81,10 @@ enum NSENotification {
// or when background notification is received.
class NSEThreads {
static let shared = NSEThreads()
private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock")
static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock")
private var allThreads: Set<UUID> = []
private var activeThreads: [(UUID, NotificationService)] = []
var activeThreads: [(UUID, NotificationService)] = []
var droppedNotifications: [(ChatId, NSENotificationData)] = []
func newThread() -> UUID {
NSEThreads.queue.sync {
@@ -64,22 +103,19 @@ class NSEThreads {
}
}
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
}
func processNotification(_ id: ChatId, _ ntf: NSENotificationData) async -> Void {
if let (_, nse) = rcvEntityThread(id),
nse.expectedMessages[id]?.shouldProcessNtf ?? false {
nse.processReceivedNtf(id, ntf, signalReady: true)
}
}
private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? {
NSEThreads.queue.sync {
activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id })
// this selects the earliest thread that:
// 1) has this connection in nse.expectedMessages
// 2) has not completed processing messages for this connection (not ready)
activeThreads.first(where: { (_, nse) in nse.expectedMessages[id]?.ready == false })
}
}
@@ -106,31 +142,38 @@ class NSEThreads {
}
}
struct ExpectedMessage {
var ntfConn: UserNtfConn
var receiveConnId: String?
var expectedMsgId: String?
var allowedGetNextAttempts: Int
var msgBestAttemptNtf: NSENotificationData?
var ready: Bool
var shouldProcessNtf: Bool
var startedProcessingNewMsgs: Bool
var semaphore: DispatchSemaphore
}
// Notification service extension creates a new instance of the class and calls didReceive for each notification.
// Each didReceive is called in its own thread, but multiple calls can be made in one process, and, empirically, there is never
// more than one process of notification service extension exists at a time.
// Soon after notification service delivers the last notification it is either suspended or terminated.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptNtf: NSENotification?
// served as notification if no message attempts (msgBestAttemptNtf) could be produced
var serviceBestAttemptNtf: NSENotification?
var badgeCount: Int = 0
// 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 receiveConnId: String?
var expectedMessage: String?
var allowedGetNextAttempts: Int = 3
// return true if the message is taken - it prevents sending it to another NotificationService instance for processing
var shouldProcessNtf = false
var expectedMessages: Dictionary<String, ExpectedMessage> = [:] // key is receiveEntityId
var appSubscriber: AppSubscriber?
var returnedSuspension = false
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("DEBUGGING: NotificationService.didReceive")
let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() }
setBestAttemptNtf(ntf)
setServiceBestAttemptNtf(ntf)
self.contentHandler = contentHandler
registerGroupDefaults()
let appState = appStateGroupDefault.get()
@@ -138,13 +181,11 @@ class NotificationService: UNNotificationServiceExtension {
switch appState {
case .stopped:
setBadgeCount()
setBestAttemptNtf(createAppStoppedNtf())
setServiceBestAttemptNtf(createAppStoppedNtf(badgeCount))
deliverBestAttemptNtf()
case .suspended:
setBadgeCount()
receiveNtfMessages(request, contentHandler)
case .suspending:
setBadgeCount()
Task {
let state: AppState = await withCheckedContinuation { cont in
appSubscriber = appStateSubscriber { s in
@@ -171,8 +212,9 @@ class NotificationService: UNNotificationServiceExtension {
deliverBestAttemptNtf()
}
}
default:
deliverBestAttemptNtf()
case .active: contentHandler(UNMutableNotificationContent())
case .activating: contentHandler(UNMutableNotificationContent())
case .bgRefresh: contentHandler(UNMutableNotificationContent())
}
}
@@ -192,78 +234,165 @@ class NotificationService: UNNotificationServiceExtension {
if let t = threadId { NSEThreads.shared.startThread(t, self) }
let dbStatus = startChat()
if case .ok = dbStatus,
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.receivedMsg_ == nil ? 0 : 1))")
if let connEntity = ntfInfo.connEntity_ {
setBestAttemptNtf(
ntfInfo.ntfsEnabled
? .nse(createConnectionEventNtf(ntfInfo.user, connEntity))
: .empty
)
if let id = connEntity.id, ntfInfo.expectedMsg_ != nil {
notificationInfo = ntfInfo
receiveEntityId = id
receiveConnId = connEntity.conn.agentConnId
let expectedMsgId = ntfInfo.expectedMsg_?.msgId
let receivedMsgId = ntfInfo.receivedMsg_?.msgId
logger.debug("NotificationService: receiveNtfMessages: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private), receivedMsgId = \(receivedMsgId ?? "nil", privacy: .private)")
expectedMessage = expectedMsgId
shouldProcessNtf = true
return
let ntfConns = apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo) {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)")
for ntfConn in ntfConns {
addExpectedMessage(ntfConn: ntfConn)
}
let connIdsToGet = expectedMessages.compactMap { (id, _) in
let started = NSEThreads.queue.sync {
let canStart = checkCanStart(id)
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: can start: \(canStart)") }
if canStart {
processDroppedNotifications(id)
expectedMessages[id]?.startedProcessingNewMsgs = true
expectedMessages[id]?.shouldProcessNtf = true
}
return canStart
}
if started {
return expectedMessages[id]?.receiveConnId
} else {
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") }
expectedMessages[id]?.semaphore.wait()
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) proceeding after semaphore") }
Task {
NSEThreads.queue.sync {
processDroppedNotifications(id)
expectedMessages[id]?.startedProcessingNewMsgs = true
expectedMessages[id]?.shouldProcessNtf = true
}
if let connId = expectedMessages[id]?.receiveConnId {
let _ = getConnNtfMessage(connId: connId)
}
}
return nil
}
}
if !connIdsToGet.isEmpty {
if let r = apiGetConnNtfMessages(connIds: connIdsToGet) {
logger.debug("NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \(r.count)")
}
return
}
} else if let dbStatus = dbStatus {
setBestAttemptNtf(createErrorNtf(dbStatus))
setServiceBestAttemptNtf(createErrorNtf(dbStatus, badgeCount))
}
}
deliverBestAttemptNtf()
}
func addExpectedMessage(ntfConn: UserNtfConn) {
if let connEntity = ntfConn.connEntity_,
let receiveEntityId = connEntity.id, ntfConn.expectedMsg_ != nil {
let expectedMsgId = ntfConn.expectedMsg_?.msgId
logger.debug("NotificationService: addExpectedMessage: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private)")
expectedMessages[receiveEntityId] = ExpectedMessage(
ntfConn: ntfConn,
receiveConnId: connEntity.conn.agentConnId,
expectedMsgId: expectedMsgId,
allowedGetNextAttempts: 3,
msgBestAttemptNtf: ntfConn.defaultBestAttemptNtf,
ready: false,
shouldProcessNtf: false,
startedProcessingNewMsgs: false,
semaphore: DispatchSemaphore(value: 0)
)
}
}
func checkCanStart(_ entityId: String) -> Bool {
return !NSEThreads.shared.activeThreads.contains(where: {
(tId, nse) in tId != threadId && nse.expectedMessages.contains(where: { $0.key == entityId })
})
}
func processDroppedNotifications(_ entityId: String) {
if !NSEThreads.shared.droppedNotifications.isEmpty {
let messagesToProcess = NSEThreads.shared.droppedNotifications.filter { (eId, _) in eId == entityId }
NSEThreads.shared.droppedNotifications.removeAll(where: { (eId, _) in eId == entityId })
for (index, (_, ntf)) in messagesToProcess.enumerated() {
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entity \(entityId, privacy: .private): processing dropped notification \(index, privacy: .private)") }
processReceivedNtf(entityId, ntf, signalReady: false)
}
}
}
override func serviceExtensionTimeWillExpire() {
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
deliverBestAttemptNtf(urgent: true)
}
func processReceivedNtf(_ ntf: NSENotification) -> Bool {
guard let ntfInfo = notificationInfo, let expectedMsgTs = ntfInfo.expectedMsg_?.msgTs else { return false }
if !ntfInfo.user.showNotifications {
self.setBestAttemptNtf(.empty)
var expectingMoreMessages: Bool {
!expectedMessages.allSatisfy { $0.value.ready }
}
func processReceivedNtf(_ id: ChatId, _ ntf: NSENotificationData, signalReady: Bool) {
guard let expectedMessage = expectedMessages[id] else {
return
}
guard let expectedMsgTs = expectedMessage.ntfConn.expectedMsg_?.msgTs else {
NSEThreads.shared.droppedNotifications.append((id, ntf))
if signalReady { entityReady(id) }
return
}
if case let .msgInfo(info) = ntf {
if info.msgId == expectedMessage {
expectedMessage = nil
if info.msgId == expectedMessage.expectedMsgId {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected")
expectedMessages[id]?.expectedMsgId = nil
if signalReady { entityReady(id) }
self.deliverBestAttemptNtf()
return true
} else if let msgTs = info.msgTs_, msgTs > expectedMsgTs {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, let other instance to process it, stopping this one")
NSEThreads.shared.droppedNotifications.append((id, ntf))
if signalReady { entityReady(id) }
self.deliverBestAttemptNtf()
return false
} else if allowedGetNextAttempts > 0, let receiveConnId = receiveConnId {
} else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let receiveConnId = expectedMessages[id]?.receiveConnId {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message")
allowedGetNextAttempts -= 1
if let receivedMsg = apiGetConnNtfMessage(connId: receiveConnId) {
logger.debug("NotificationService processNtf, on apiGetConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)")
return true
expectedMessages[id]?.allowedGetNextAttempts -= 1
if let receivedMsg = getConnNtfMessage(connId: receiveConnId) {
logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)")
} else {
logger.debug("NotificationService processNtf, on apiGetConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt")
logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt")
NSEThreads.shared.droppedNotifications.append((id, ntf))
if signalReady { entityReady(id) }
self.deliverBestAttemptNtf()
return false
}
} else {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unknown message, let other instance to process it")
NSEThreads.shared.droppedNotifications.append((id, ntf))
if signalReady { entityReady(id) }
self.deliverBestAttemptNtf()
return false
}
} else if ntfInfo.user.showNotifications {
} else if expectedMessage.ntfConn.user.showNotifications {
logger.debug("NotificationService processNtf: setting best attempt")
self.setBestAttemptNtf(ntf)
if ntf.isCallInvitation {
self.deliverBestAttemptNtf()
if ntf.notificationEvent != nil {
setBadgeCount()
}
return true
let prevBestAttempt = expectedMessages[id]?.msgBestAttemptNtf
if prevBestAttempt?.callInvitation != nil {
if ntf.callInvitation != nil { // replace with newer call
expectedMessages[id]?.msgBestAttemptNtf = ntf
} // otherwise keep call as best attempt
} else {
expectedMessages[id]?.msgBestAttemptNtf = ntf
}
} else {
NSEThreads.shared.droppedNotifications.append((id, ntf))
if signalReady { entityReady(id) }
}
}
func entityReady(_ entityId: ChatId) {
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: entity \(entityId, privacy: .private)") }
expectedMessages[entityId]?.ready = true
if let (tNext, nse) = NSEThreads.shared.activeThreads.first(where: { (_, nse) in nse.expectedMessages[entityId]?.startedProcessingNewMsgs == false }) {
if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: signal next thread \(tNext, privacy: .private) for entity \(entityId, privacy: .private)") }
nse.expectedMessages[entityId]?.semaphore.signal()
}
return false
}
func setBadgeCount() {
@@ -271,37 +400,32 @@ class NotificationService: UNNotificationServiceExtension {
ntfBadgeCountGroupDefault.set(badgeCount)
}
func setBestAttemptNtf(_ ntf: UNMutableNotificationContent) {
setBestAttemptNtf(.nse(ntf))
}
func setBestAttemptNtf(_ ntf: NSENotification) {
logger.debug("NotificationService.setBestAttemptNtf")
if case let .nse(notification) = ntf {
notification.badge = badgeCount as NSNumber
bestAttemptNtf = .nse(notification)
} else {
bestAttemptNtf = ntf
}
func setServiceBestAttemptNtf(_ ntf: UNMutableNotificationContent) {
logger.debug("NotificationService.setServiceBestAttemptNtf")
serviceBestAttemptNtf = .nse(ntf)
}
private func deliverBestAttemptNtf(urgent: Bool = false) {
logger.debug("NotificationService.deliverBestAttemptNtf")
// stop processing other messages
shouldProcessNtf = false
if (urgent || !expectingMoreMessages) {
logger.debug("NotificationService.deliverBestAttemptNtf")
// stop processing other messages
for (key, _) in expectedMessages {
expectedMessages[key]?.shouldProcessNtf = false
}
let suspend: Bool
if let t = threadId {
threadId = nil
suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads
} else {
suspend = false
let suspend: Bool
if let t = threadId {
threadId = nil
suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads
} else {
suspend = false
}
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
}
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
}
private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) {
if case .callkit = bestAttemptNtf {
if useCallKit() && expectedMessages.contains(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }) {
logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit")
if urgent {
// suspending NSE even though there may be other notifications
@@ -339,19 +463,13 @@ class NotificationService: UNNotificationServiceExtension {
}
private func deliverNotification() {
if let handler = contentHandler, let ntf = bestAttemptNtf {
if let handler = contentHandler, let ntf = prepareNotification() {
contentHandler = nil
bestAttemptNtf = nil
let deliver: (UNMutableNotificationContent?) -> Void = { ntf in
let useNtf = if let ntf = ntf {
appStateGroupDefault.get().running ? UNMutableNotificationContent() : ntf
} else {
UNMutableNotificationContent()
}
handler(useNtf)
}
serviceBestAttemptNtf = nil
switch ntf {
case let .nse(content): deliver(content)
case let .nse(content):
content.badge = badgeCount as NSNumber
handler(content)
case let .callkit(invitation):
logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)")
CXProvider.reportNewIncomingVoIPPushPayload([
@@ -362,13 +480,85 @@ class NotificationService: UNNotificationServiceExtension {
"callTs": invitation.callTs.timeIntervalSince1970
]) { error in
logger.debug("reportNewIncomingVoIPPushPayload result: \(error)")
deliver(error == nil ? nil : createCallInvitationNtf(invitation))
handler(error == nil ? UNMutableNotificationContent() : createCallInvitationNtf(invitation, self.badgeCount))
}
case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo
case .empty:
handler(UNMutableNotificationContent()) // used to mute notifications that did not unsubscribe yet
}
}
}
private func prepareNotification() -> NSENotification? {
if expectedMessages.isEmpty {
return serviceBestAttemptNtf
} else if let callNtfKV = expectedMessages.first(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }),
let callInv = callNtfKV.value.msgBestAttemptNtf?.callInvitation,
let callNtf = callNtfKV.value.msgBestAttemptNtf {
return useCallKit() ? .callkit(callInv) : .nse(callNtf.notificationContent(badgeCount))
} else {
let ntfEvents = expectedMessages.compactMap { $0.value.msgBestAttemptNtf?.notificationEvent }
if ntfEvents.isEmpty {
return .empty
} else if let ntfEvent = ntfEvents.count == 1 ? ntfEvents.first : nil {
return .nse(ntfEvent.notificationContent(badgeCount))
} else {
return .nse(createJointNtf(ntfEvents))
}
}
}
private func createJointNtf(_ ntfEvents: [NSENotificationData]) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
let newMsgsData: [(any UserLike, ChatInfo)] = ntfEvents.compactMap { $0.newMsgData }
if !newMsgsData.isEmpty, let userId = newMsgsData.first?.0.userId {
let newMsgsChats: [ChatInfo] = newMsgsData.map { $0.1 }
let uniqueChatsNames = uniqueNewMsgsChatsNames(newMsgsChats)
var body: String
if previewMode == .hidden {
body = String.localizedStringWithFormat(NSLocalizedString("New messages in %d chats", comment: "notification body"), uniqueChatsNames.count)
} else {
body = String.localizedStringWithFormat(NSLocalizedString("From: %@", comment: "notification body"), newMsgsChatsNamesStr(uniqueChatsNames))
}
return createNotification(
categoryIdentifier: ntfCategoryManyEvents,
title: NSLocalizedString("New messages", comment: "notification"),
body: body,
userInfo: ["userId": userId],
badgeCount: badgeCount
)
} else {
return createNotification(
categoryIdentifier: ntfCategoryManyEvents,
title: NSLocalizedString("New events", comment: "notification"),
body: String.localizedStringWithFormat(NSLocalizedString("%d new events", comment: "notification body"), ntfEvents.count),
badgeCount: badgeCount
)
}
}
private func uniqueNewMsgsChatsNames(_ newMsgsChats: [ChatInfo]) -> [String] {
var seenChatIds = Set<ChatId>()
var uniqueChatsNames: [String] = []
for chat in newMsgsChats {
if !seenChatIds.contains(chat.id) {
seenChatIds.insert(chat.id)
uniqueChatsNames.append(chat.chatViewName)
}
}
return uniqueChatsNames
}
private func newMsgsChatsNamesStr(_ names: [String]) -> String {
return switch names.count {
case 1: names[0]
case 2: "\(names[0]) and \(names[1])"
case 3: "\(names[0] + ", " + names[1]) and \(names[2])"
default:
names.count > 3
? "\(names[0]), \(names[1]) and \(names.count - 2) other chats"
: ""
}
}
}
// nseStateGroupDefault must not be used in NSE directly, only via this singleton
@@ -582,28 +772,25 @@ func chatRecvMsg() async -> ChatResponse? {
private let isInChina = SKStorefront().countryCode == "CHN"
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotificationData)? {
logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
switch res {
case let .contactConnected(user, contact, _):
return (contact.id, .nse(createContactConnectedNtf(user, contact)))
return (contact.id, .contactConnected(user, contact))
// case let .contactConnecting(contact):
// TODO profile update
case let .receivedContactRequest(user, contactRequest):
return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest)))
return (UserContact(contactRequest: contactRequest).id, .contactRequest(user, contactRequest))
case let .newChatItems(user, chatItems):
// Received items are created one at a time
if let chatItem = chatItems.first {
let cInfo = chatItem.chatInfo
var cItem = chatItem.chatItem
if !cInfo.ntfsEnabled {
ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1))
}
if let file = cItem.autoReceiveFile() {
cItem = autoReceiveFile(file) ?? cItem
}
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty
return cItem.showNotification ? (chatItem.chatId, ntf) : nil
let ntf: NSENotificationData = (cInfo.ntfsEnabled && cItem.showNotification) ? .messageReceived(user, cInfo, cItem) : .noNtf
return (chatItem.chatId, ntf)
} else {
return nil
}
@@ -620,10 +807,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
return nil
case let .callInvitation(invitation):
// Do not post it without CallKit support, iOS will stop launching the app without showing CallKit
return (
invitation.contact.id,
useCallKit() ? .callkit(invitation) : .nse(createCallInvitationNtf(invitation))
)
return (invitation.contact.id, .callInvitation(invitation))
case let .ntfMessage(_, connEntity, ntfMessage):
return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil }
case .chatSuspended:
@@ -704,15 +888,15 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws {
throw r
}
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? {
guard apiGetActiveUser() != nil else {
logger.debug("no active user")
return nil
}
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo))
if case let .ntfMessages(user, connEntity_, expectedMsg_, receivedMsg_) = r, let user = user {
logger.debug("apiGetNtfMessage response ntfMessages: \(receivedMsg_ == nil ? 0 : 1)")
return NtfMessages(user: user, connEntity_: connEntity_, expectedMsg_: expectedMsg_, receivedMsg_: receivedMsg_)
let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo))
if case let .ntfConns(ntfConns) = r {
logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)")
return ntfConns.compactMap { toUserNtfConn($0) }
} else if case let .chatCmdError(_, error) = r {
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
} else {
@@ -721,17 +905,33 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
return nil
}
func apiGetConnNtfMessage(connId: String) -> NtfMsgInfo? {
func toUserNtfConn(_ ntfConn: NtfConn) -> UserNtfConn? {
if let user = ntfConn.user_ {
return UserNtfConn(user: user, connEntity_: ntfConn.connEntity_, expectedMsg_: ntfConn.expectedMsg_)
} else {
return nil
}
}
func apiGetConnNtfMessages(connIds: [String]) -> [NtfMsgInfo?]? {
guard apiGetActiveUser() != nil else {
logger.debug("no active user")
return nil
}
let r = sendSimpleXCmd(.apiGetConnNtfMessage(connId: connId))
if case let .connNtfMessage(receivedMsg_) = r {
logger.debug("apiGetConnNtfMessage response receivedMsg_: \(receivedMsg_ == nil ? 0 : 1)")
return receivedMsg_
let r = sendSimpleXCmd(.apiGetConnNtfMessages(connIds: connIds))
if case let .connNtfMessages(receivedMsgs) = r {
logger.debug("apiGetConnNtfMessages response receivedMsgs: \(receivedMsgs.count)")
return receivedMsgs
}
logger.debug("apiGetConnNtfMessages error: \(responseError(r))")
return nil
}
func getConnNtfMessage(connId: String) -> NtfMsgInfo? {
let r_ = apiGetConnNtfMessages(connIds: [connId])
if let r = r_, let receivedMsg = r.count == 1 ? r.first : nil {
return receivedMsg
}
logger.debug("apiGetConnNtfMessage error: \(responseError(r))")
return nil
}
@@ -769,13 +969,33 @@ func setNetworkConfig(_ cfg: NetCfg) throws {
throw r
}
struct NtfMessages {
struct UserNtfConn {
var user: User
var connEntity_: ConnectionEntity?
var expectedMsg_: NtfMsgInfo?
var receivedMsg_: NtfMsgInfo?
var ntfsEnabled: Bool {
user.showNotifications && (connEntity_?.ntfsEnabled ?? false)
var defaultBestAttemptNtf: NSENotificationData {
return if !user.showNotifications {
.noNtf
} else if let connEntity = connEntity_ {
switch connEntity {
case let .rcvDirectMsgConnection(_, contact):
contact?.chatSettings.enableNtfs == .all
? .connectionEvent(user, connEntity)
: .noNtf
case let .rcvGroupMsgConnection(_, groupInfo, _):
groupInfo.chatSettings.enableNtfs == .all
? .connectionEvent(user, connEntity)
: .noNtf
case .sndFileConnection: .noNtf
case .rcvFileConnection: .noNtf
case let .userContactConnection(_, userContact):
userContact.groupId == nil
? .connectionEvent(user, connEntity)
: .noNtf
}
} else {
.noNtf
}
}
}
+44 -40
View File
@@ -144,15 +144,15 @@
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; };
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; };
640548A32CB56735005DE1E4 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6405489E2CB56735005DE1E4 /* libgmpxx.a */; };
640548A42CB56735005DE1E4 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6405489F2CB56735005DE1E4 /* libgmp.a */; };
640548A52CB56735005DE1E4 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A02CB56735005DE1E4 /* libffi.a */; };
640548A62CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */; };
640548A72CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */; };
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; };
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; };
643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; };
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; };
643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; };
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; };
@@ -209,6 +209,7 @@
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; };
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; };
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; };
CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */; };
CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; };
CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; };
CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; };
@@ -486,15 +487,15 @@
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
6405489E2CB56735005DE1E4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
6405489F2CB56735005DE1E4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
640548A02CB56735005DE1E4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a"; sourceTree = "<group>"; };
640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a"; sourceTree = "<group>"; };
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = "<group>"; };
643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = "<group>"; };
643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
@@ -550,6 +551,7 @@
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; };
CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = "<group>"; };
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = "<group>"; };
CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = "<group>"; };
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = "<group>"; };
CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = "<group>"; };
CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -655,14 +657,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
640548A32CB56735005DE1E4 /* libgmpxx.a in Frameworks */,
640548A72CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a in Frameworks */,
640548A52CB56735005DE1E4 /* libffi.a in Frameworks */,
640548A42CB56735005DE1E4 /* libgmp.a in Frameworks */,
643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */,
643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */,
643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
640548A62CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */,
643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -739,11 +741,6 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
640548A02CB56735005DE1E4 /* libffi.a */,
6405489F2CB56735005DE1E4 /* libgmp.a */,
6405489E2CB56735005DE1E4 /* libgmpxx.a */,
640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */,
640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -802,6 +799,7 @@
CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */,
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */,
CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */,
CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -814,6 +812,11 @@
5CC2C0FA2809BF11000C35E3 /* Localizable.strings */,
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */,
5C764E5C279C70B7000C6508 /* Libraries */,
643B3B422CCBEB080083A2CF /* libffi.a */,
643B3B442CCBEB080083A2CF /* libgmp.a */,
643B3B402CCBEB080083A2CF /* libgmpxx.a */,
643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */,
643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */,
5CA059C2279559F40002BEB4 /* Shared */,
5CDCAD462818589900503DA2 /* SimpleX NSE */,
CEE723A82C3BD3D70009AE93 /* SimpleX SE */,
@@ -1482,6 +1485,7 @@
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */,
CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */,
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
@@ -1899,7 +1903,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1924,7 +1928,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1948,7 +1952,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1973,7 +1977,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1989,11 +1993,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2009,11 +2013,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2034,7 +2038,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2049,7 +2053,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2071,7 +2075,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2086,7 +2090,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2108,7 +2112,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2134,7 +2138,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2159,7 +2163,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2185,7 +2189,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2210,7 +2214,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2225,7 +2229,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2244,7 +2248,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 242;
CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2259,7 +2263,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.1;
MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
+14 -13
View File
@@ -55,8 +55,8 @@ public enum ChatCommand {
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
case apiDeleteToken(token: DeviceToken)
case apiGetNtfMessage(nonce: String, encNtfInfo: String)
case apiGetConnNtfMessage(connId: String)
case apiGetNtfConns(nonce: String, encNtfInfo: String)
case apiGetConnNtfMessages(connIds: [String])
case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile)
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
case apiJoinGroup(groupId: Int64)
@@ -214,8 +214,8 @@ public enum ChatCommand {
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)"
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)"
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)"
case let .apiGetConnNtfMessage(connId): return "/_ntf conn message \(connId)"
case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)"
case let .apiGetConnNtfMessages(connIds): return "/_ntf conn messages \(connIds.joined(separator: ","))"
case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))"
case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
@@ -369,8 +369,8 @@ public enum ChatCommand {
case .apiRegisterToken: return "apiRegisterToken"
case .apiVerifyToken: return "apiVerifyToken"
case .apiDeleteToken: return "apiDeleteToken"
case .apiGetNtfMessage: return "apiGetNtfMessage"
case .apiGetConnNtfMessage: return "apiGetConnNtfMessage"
case .apiGetNtfConns: return "apiGetNtfConns"
case .apiGetConnNtfMessages: return "apiGetConnNtfMessages"
case .apiNewGroup: return "apiNewGroup"
case .apiAddMember: return "apiAddMember"
case .apiJoinGroup: return "apiJoinGroup"
@@ -682,8 +682,8 @@ public enum ChatResponse: Decodable, Error {
case callInvitations(callInvitations: [RcvCallInvitation])
case ntfTokenStatus(status: NtfTknStatus)
case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String)
case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, expectedMsg_: NtfMsgInfo?, receivedMsg_: NtfMsgInfo?)
case connNtfMessage(receivedMsg_: NtfMsgInfo?)
case ntfConns(ntfConns: [NtfConn])
case connNtfMessages(receivedMsgs: [NtfMsgInfo?])
case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case contactDisabled(user: UserRef, contact: Contact)
@@ -851,8 +851,8 @@ public enum ChatResponse: Decodable, Error {
case .callInvitations: return "callInvitations"
case .ntfTokenStatus: return "ntfTokenStatus"
case .ntfToken: return "ntfToken"
case .ntfMessages: return "ntfMessages"
case .connNtfMessage: return "connNtfMessage"
case .ntfConns: return "ntfConns"
case .connNtfMessages: return "connNtfMessages"
case .ntfMessage: return "ntfMessage"
case .contactConnectionDeleted: return "contactConnectionDeleted"
case .contactDisabled: return "contactDisabled"
@@ -1029,8 +1029,8 @@ public enum ChatResponse: Decodable, Error {
case let .callInvitations(invs): return String(describing: invs)
case let .ntfTokenStatus(status): return String(describing: status)
case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)"
case let .ntfMessages(u, connEntity, expectedMsg_, receivedMsg_): return withUser(u, "connEntity: \(String(describing: connEntity))\nexpectedMsg_: \(String(describing: expectedMsg_))\nreceivedMsg_: \(String(describing: receivedMsg_))")
case let .connNtfMessage(receivedMsg_): return "receivedMsg_: \(String(describing: receivedMsg_))"
case let .ntfConns(ntfConns): return String(describing: ntfConns)
case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))"
case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))")
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .contactDisabled(u, contact): return withUser(u, String(describing: contact))
@@ -2041,7 +2041,7 @@ public enum SQLiteError: Decodable, Hashable {
}
public enum AgentErrorType: Decodable, Hashable {
case CMD(cmdErr: CommandErrorType)
case CMD(cmdErr: CommandErrorType, errContext: String)
case CONN(connErr: ConnectionErrorType)
case SMP(serverAddress: String, smpErr: ProtocolErrorType)
case NTF(ntfErr: ProtocolErrorType)
@@ -2088,6 +2088,7 @@ public enum ProtocolErrorType: Decodable, Hashable {
case AUTH
case CRYPTO
case QUOTA
case STORE(storeErr: String)
case NO_MSG
case LARGE_MSG
case EXPIRED
+1 -1
View File
@@ -85,7 +85,7 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false,
GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED: true,
GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false,
GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: false,
GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: true,
GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false,
GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true,
+8 -9
View File
@@ -1852,6 +1852,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable {
public enum ConnStatus: String, Decodable, Hashable {
case new = "new"
case prepared = "prepared"
case joined = "joined"
case requested = "requested"
case accepted = "accepted"
@@ -1863,6 +1864,7 @@ public enum ConnStatus: String, Decodable, Hashable {
get {
switch self {
case .new: return true
case .prepared: return false
case .joined: return false
case .requested: return true
case .accepted: return true
@@ -2268,16 +2270,13 @@ public enum ConnectionEntity: Decodable, Hashable {
case let .userContactConnection(entityConnection, _): entityConnection
}
}
}
public struct NtfConn: Decodable, Hashable {
public var user_: User?
public var connEntity_: ConnectionEntity?
public var expectedMsg_: NtfMsgInfo?
public var ntfsEnabled: Bool {
switch self {
case let .rcvDirectMsgConnection(_, contact): return contact?.chatSettings.enableNtfs == .all
case let .rcvGroupMsgConnection(_, groupInfo, _): return groupInfo.chatSettings.enableNtfs == .all
case .sndFileConnection: return false
case .rcvFileConnection: return false
case let .userContactConnection(_, userContact): return userContact.groupId == nil
}
}
}
public struct NtfMsgInfo: Decodable, Hashable {
+32 -16
View File
@@ -15,13 +15,14 @@ public let ntfCategoryContactConnected = "NTF_CAT_CONTACT_CONNECTED"
public let ntfCategoryMessageReceived = "NTF_CAT_MESSAGE_RECEIVED"
public let ntfCategoryCallInvitation = "NTF_CAT_CALL_INVITATION"
public let ntfCategoryConnectionEvent = "NTF_CAT_CONNECTION_EVENT"
public let ntfCategoryManyEvents = "NTF_CAT_MANY_EVENTS"
public let ntfCategoryCheckMessage = "NTF_CAT_CHECK_MESSAGE"
public let appNotificationId = "chat.simplex.app.notification"
let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification")
public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest) -> UNMutableNotificationContent {
public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
categoryIdentifier: ntfCategoryContactRequest,
@@ -34,11 +35,12 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName
),
targetContentIdentifier: nil,
userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId]
userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId],
badgeCount: badgeCount
)
}
public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) -> UNMutableNotificationContent {
public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
categoryIdentifier: ntfCategoryContactConnected,
@@ -51,12 +53,13 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact)
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName
),
targetContentIdentifier: contact.id,
userInfo: ["userId": user.userId]
userInfo: ["userId": user.userId],
// userInfo: ["chatId": contact.id, "contactId": contact.apiId]
badgeCount: badgeCount
)
}
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent {
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
@@ -69,12 +72,13 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _
title: title,
body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"),
targetContentIdentifier: cInfo.id,
userInfo: ["userId": user.userId]
userInfo: ["userId": user.userId],
// userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id]
badgeCount: badgeCount
)
}
public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutableNotificationContent {
public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCount: Int) -> UNMutableNotificationContent {
let text = invitation.callType.media == .video
? NSLocalizedString("Incoming video call", comment: "notification")
: NSLocalizedString("Incoming audio call", comment: "notification")
@@ -84,11 +88,12 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutabl
title: hideContent ? contactHidden : "\(invitation.contact.chatViewName):",
body: text,
targetContentIdentifier: nil,
userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId]
userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId],
badgeCount: badgeCount
)
}
public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity) -> UNMutableNotificationContent {
public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
var title: String
var body: String? = nil
@@ -118,11 +123,12 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit
title: title,
body: body,
targetContentIdentifier: targetContentIdentifier,
userInfo: ["userId": user.userId]
userInfo: ["userId": user.userId],
badgeCount: badgeCount
)
}
public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificationContent {
public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> UNMutableNotificationContent {
var title: String
switch dbStatus {
case .errorNotADatabase:
@@ -142,14 +148,16 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificati
}
return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent,
title: title
title: title,
badgeCount: badgeCount
)
}
public func createAppStoppedNtf() -> UNMutableNotificationContent {
public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent {
return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent,
title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification")
title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification"),
badgeCount: badgeCount
)
}
@@ -159,8 +167,15 @@ private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember
: "#\(groupInfo.displayName) \(groupMember.chatViewName):"
}
public func createNotification(categoryIdentifier: String, title: String, subtitle: String? = nil, body: String? = nil,
targetContentIdentifier: String? = nil, userInfo: [AnyHashable : Any] = [:]) -> UNMutableNotificationContent {
public func createNotification(
categoryIdentifier: String,
title: String,
subtitle: String? = nil,
body: String? = nil,
targetContentIdentifier: String? = nil,
userInfo: [AnyHashable : Any] = [:],
badgeCount: Int
) -> UNMutableNotificationContent {
let content = UNMutableNotificationContent()
content.categoryIdentifier = categoryIdentifier
content.title = title
@@ -170,6 +185,7 @@ public func createNotification(categoryIdentifier: String, title: String, subtit
content.userInfo = userInfo
// TODO move logic of adding sound here, so it applies to background notifications too
content.sound = .default
content.badge = badgeCount as NSNumber
// content.interruptionLevel = .active
// content.relevanceScore = 0.5 // 0-1
return content
+36
View File
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "Falsche Nachrichten-ID";
/* No comment provided by engineer. */
"Better calls" = "Verbesserte Anrufe";
/* No comment provided by engineer. */
"Better groups" = "Bessere Gruppen";
/* No comment provided by engineer. */
"Better message dates." = "Verbesserte Nachrichten-Datumsinformation";
/* No comment provided by engineer. */
"Better messages" = "Verbesserungen bei Nachrichten";
/* No comment provided by engineer. */
"Better networking" = "Kontrollieren Sie Ihr Netzwerk";
/* No comment provided by engineer. */
"Better notifications" = "Verbesserte Benachrichtigungen";
/* No comment provided by engineer. */
"Better security ✅" = "Verbesserte Sicherheit ✅";
/* No comment provided by engineer. */
"Better user experience" = "Verbesserte Nutzer-Erfahrung";
/* No comment provided by engineer. */
"Black" = "Schwarz";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Zeit anpassen";
/* No comment provided by engineer. */
"Customizable message shape." = "Anpassbares Format des Nachrichtenfelds";
/* No comment provided by engineer. */
"Customize theme" = "Design anpassen";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Alte Datenbank löschen?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Bis zu 200 Nachrichten löschen oder moderieren";
/* No comment provided by engineer. */
"Delete pending connection?" = "Ausstehende Verbindung löschen?";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Nachrichten ohne Dateien weiterleiten?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Bis zu 20 Nachrichten auf einmal weiterleiten";
/* No comment provided by engineer. */
"forwarded" = "weitergeleitet";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Archiv wird importiert";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch.\nWeitere Verbesserungen sind bald verfügbar!";
/* No comment provided by engineer. */
"Improved message delivery" = "Verbesserte Zustellung von Nachrichten";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "SimpleX-Einmal-Einladung";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Vereinfachter Inkognito-Modus";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Unterstützung von SimpleX Chat";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Während des Anrufs zwischen Audio und Video wechseln";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Das Chat-Profil für Einmal-Einladungen wechseln";
/* No comment provided by engineer. */
"System" = "System";
+36
View File
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "ID de mensaje incorrecto";
/* No comment provided by engineer. */
"Better calls" = "Llamadas mejoradas";
/* No comment provided by engineer. */
"Better groups" = "Grupos mejorados";
/* No comment provided by engineer. */
"Better message dates." = "Sistema de fechas mejorado.";
/* No comment provided by engineer. */
"Better messages" = "Mensajes mejorados";
/* No comment provided by engineer. */
"Better networking" = "Uso de red mejorado";
/* No comment provided by engineer. */
"Better notifications" = "Notificaciones mejoradas";
/* No comment provided by engineer. */
"Better security ✅" = "Seguridad mejorada ✅";
/* No comment provided by engineer. */
"Better user experience" = "Experiencia de usuario mejorada";
/* No comment provided by engineer. */
"Black" = "Negro";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Tiempo personalizado";
/* No comment provided by engineer. */
"Customizable message shape." = "Forma personalizable de los mensajes.";
/* No comment provided by engineer. */
"Customize theme" = "Personalizar tema";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "¿Eliminar base de datos antigua?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Borra o modera hasta 200 mensajes a la vez.";
/* No comment provided by engineer. */
"Delete pending connection?" = "¿Eliminar conexión pendiente?";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "¿Reenviar mensajes sin los archivos?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Desplazamiento de hasta 20 mensajes.";
/* No comment provided by engineer. */
"forwarded" = "reenviado";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Importando archivo";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Reducción del tráfico y entrega mejorada.\n¡Pronto habrá nuevas mejoras!";
/* No comment provided by engineer. */
"Improved message delivery" = "Entrega de mensajes mejorada";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "Invitación SimpleX de un uso";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Modo incógnito simplificado";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Soporte SimpleX Chat";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Intercambia audio y video durante la llamada.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Cambia el perfil de chat para invitaciones de un solo uso.";
/* No comment provided by engineer. */
"System" = "Sistema";
+70 -34
View File
@@ -479,7 +479,7 @@
"All your contacts will remain connected." = "Minden ismerősével kapcsolatban marad.";
/* No comment provided by engineer. */
"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.";
"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél.";
/* No comment provided by engineer. */
"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra.";
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "Téves üzenet ID";
/* No comment provided by engineer. */
"Better calls" = "Továbbfejlesztett hívásélmény";
/* No comment provided by engineer. */
"Better groups" = "Javított csoportok";
/* No comment provided by engineer. */
"Better message dates." = "Továbbfejlesztett üzenetdátumok.";
/* No comment provided by engineer. */
"Better messages" = "Jobb üzenetek";
/* No comment provided by engineer. */
"Better networking" = "Jobb hálózatkezelés";
/* No comment provided by engineer. */
"Better notifications" = "Továbbfejlesztett értesítések";
/* No comment provided by engineer. */
"Better security ✅" = "Továbbfejlesztett biztonság ✅";
/* No comment provided by engineer. */
"Better user experience" = "Továbbfejlesztett felhasználói élmény";
/* No comment provided by engineer. */
"Black" = "Fekete";
@@ -918,7 +933,7 @@
"Chat preferences were changed." = "A csevegési beállítások megváltoztak.";
/* No comment provided by engineer. */
"Chat profile" = "Felhasználói profil";
"Chat profile" = "Csevegési profil";
/* No comment provided by engineer. */
"Chat theme" = "Csevegés témája";
@@ -933,7 +948,7 @@
"Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület";
/* No comment provided by engineer. */
"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről opciót az új eszközén és olvassa be a QR-kódot.";
"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot.";
/* No comment provided by engineer. */
"Choose file" = "Fájl kiválasztása";
@@ -1140,7 +1155,7 @@
"Connection terminated" = "Kapcsolat megszakítva";
/* No comment provided by engineer. */
"Connection timeout" = "Kapcsolat időtúllépés";
"Connection timeout" = "Időtúllépés kapcsolódáskor";
/* No comment provided by engineer. */
"Connection with desktop stopped" = "A kapcsolat a számítógéppel megszakadt";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Személyreszabott idő";
/* No comment provided by engineer. */
"Customizable message shape." = "Testreszabható üzenetbuborékok.";
/* No comment provided by engineer. */
"Customize theme" = "Téma személyre szabása";
@@ -1323,7 +1341,7 @@
"Database ID: %d" = "Adatbázis-azonosító: %d";
/* No comment provided by engineer. */
"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átviteli izolációs beállítások.";
"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitel-izolációs beállítások.";
/* No comment provided by engineer. */
"Database is encrypted using a random passphrase, you can change it." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva, ami megváltoztatható.";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Régi adatbázis törlése?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása.";
/* No comment provided by engineer. */
"Delete pending connection?" = "Függőben lévő ismerőskérelem törlése?";
@@ -1555,7 +1576,7 @@
"Detailed statistics" = "Részletes statisztikák";
/* No comment provided by engineer. */
"Details" = "Részletek";
"Details" = "További részletek";
/* No comment provided by engineer. */
"Develop" = "Fejlesztés";
@@ -1579,7 +1600,7 @@
"different migration in the app/database: %@ / %@" = "különböző átköltöztetések az alkalmazásban/adatbázisban: %@ / %@";
/* No comment provided by engineer. */
"Different names, avatars and transport isolation." = "Különböző nevek, avatarok és átviteli izoláció.";
"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitel-izoláció.";
/* connection level description */
"direct" = "közvetlen";
@@ -1952,7 +1973,7 @@
"Error deleting token" = "Hiba a token törlésekor";
/* No comment provided by engineer. */
"Error deleting user profile" = "Hiba a felhasználói profil törlésekor";
"Error deleting user profile" = "Hiba a felhasználó-profil törlésekor";
/* No comment provided by engineer. */
"Error downloading the archive" = "Hiba az archívum letöltésekor";
@@ -2021,7 +2042,7 @@
"Error saving settings" = "Hiba a beállítások mentésekor";
/* No comment provided by engineer. */
"Error saving user password" = "Hiba a felhasználó jelszavának mentésekor";
"Error saving user password" = "Hiba a felhasználói jelszó mentésekor";
/* No comment provided by engineer. */
"Error scanning code: %@" = "Hiba a kód beolvasásakor: %@";
@@ -2063,7 +2084,7 @@
"Error updating settings" = "Hiba történt a beállítások frissítésekor";
/* No comment provided by engineer. */
"Error updating user privacy" = "Hiba a felhasználói beállítások frissítésekor";
"Error updating user privacy" = "Hiba a felhasználói adatvédelem frissítésekor";
/* No comment provided by engineer. */
"Error uploading the archive" = "Hiba az archívum feltöltésekor";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Üzenetek továbbítása fájlok nélkül?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Legfeljebb 20 üzenet egyszerre való továbbítása.";
/* No comment provided by engineer. */
"forwarded" = "továbbított";
@@ -2384,7 +2408,7 @@
"Hidden profile password" = "Rejtett profiljelszó";
/* chat item action */
"Hide" = "Elrejtés";
"Hide" = "Összecsukás";
/* No comment provided by engineer. */
"Hide app screen in the recent apps." = "Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között.";
@@ -2435,7 +2459,7 @@
"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:";
/* No comment provided by engineer. */
"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább az **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése).";
"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése).";
/* No comment provided by engineer. */
"Ignore" = "Mellőzés";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Archívum importálása";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat.\nTovábbi fejlesztések hamarosan!";
/* No comment provided by engineer. */
"Improved message delivery" = "Továbbfejlesztett üzenetkézbesítés";
@@ -2789,7 +2816,7 @@
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva.";
/* No comment provided by engineer. */
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*";
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*";
/* No comment provided by engineer. */
"Mark deleted for everyone" = "Jelölje meg mindenki számára töröltként";
@@ -3199,7 +3226,7 @@
"Onion hosts will not be used." = "Onion-kiszolgálók nem lesznek használva.";
/* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak az eszközök alkalmazásai tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket.";
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket.";
/* No comment provided by engineer. */
"Only delete conversation" = "Csak a beszélgetés törlése";
@@ -3265,7 +3292,7 @@
"Open Settings" = "Beállítások megnyitása";
/* authentication reason */
"Open user profiles" = "Felhasználói profilok megnyitása";
"Open user profiles" = "Felhasználó-profilok megnyitása";
/* No comment provided by engineer. */
"Open-source protocol and code anybody can run the servers." = "Nyílt forráskódú protokoll és forráskód bárki üzemeltethet kiszolgálókat.";
@@ -3361,10 +3388,10 @@
"Picture-in-picture calls" = "Kép a képben hívások";
/* No comment provided by engineer. */
"PING count" = "PING számláló";
"PING count" = "PING-ek száma";
/* No comment provided by engineer. */
"PING interval" = "PING időköze";
"PING interval" = "Időtartam a PING-ek között";
/* No comment provided by engineer. */
"Play from the chat list." = "Lejátszás a csevegési listából.";
@@ -3523,10 +3550,10 @@
"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben.\nEngedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.";
/* No comment provided by engineer. */
"Protocol timeout" = "Protokoll időtúllépés";
"Protocol timeout" = "Protokoll időtúllépése";
/* No comment provided by engineer. */
"Protocol timeout per KB" = "Protokoll időkorlát KB-onként";
"Protocol timeout per KB" = "Protokoll időtúllépése KB-onként";
/* No comment provided by engineer. */
"Proxied" = "Proxyzott";
@@ -3610,7 +3637,7 @@
"Received reply" = "Fogadott válaszüzenet-buborék színe";
/* No comment provided by engineer. */
"Received total" = "Összes fogadott";
"Received total" = "Összes fogadott üzenet";
/* No comment provided by engineer. */
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "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.";
@@ -3846,7 +3873,7 @@
"Save preferences?" = "Beállítások mentése?";
/* No comment provided by engineer. */
"Save profile password" = "Felhasználói fiók jelszavának mentése";
"Save profile password" = "Profiljelszó mentése";
/* No comment provided by engineer. */
"Save servers" = "Kiszolgálók mentése";
@@ -4074,7 +4101,7 @@
"Sent reply" = "Válaszüzenet-buborék színe";
/* No comment provided by engineer. */
"Sent total" = "Összes elküldött";
"Sent total" = "Összes elküldött üzenet";
/* No comment provided by engineer. */
"Sent via proxy" = "Proxyn keresztül küldve";
@@ -4212,7 +4239,7 @@
"Show last messages" = "Szobák utolsó üzeneteinek megjelenítése a listanézetben";
/* No comment provided by engineer. */
"Show message status" = "Üzenet állapot megjelenítése";
"Show message status" = "Üzenetállapot megjelenítése";
/* No comment provided by engineer. */
"Show percentage" = "Százalék megjelenítése";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Egyszerűsített inkognitómód";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "SimpleX Chat támogatása";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Hang/Videó váltása hívás közben.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívókhoz.";
/* No comment provided by engineer. */
"System" = "Rendszer";
@@ -4410,25 +4446,25 @@
"Tap to activate profile." = "A profil aktiválásához koppintson az ikonra.";
/* No comment provided by engineer. */
"Tap to Connect" = "Koppintson a kapcsolódáshoz";
"Tap to Connect" = "Koppintson ide a kapcsolódáshoz";
/* No comment provided by engineer. */
"Tap to join" = "Koppintson a csatlakozáshoz";
"Tap to join" = "Koppintson ide a csatlakozáshoz";
/* No comment provided by engineer. */
"Tap to join incognito" = "Koppintson az inkognitóban való csatlakozáshoz";
"Tap to join incognito" = "Koppintson ide az inkognitóban való csatlakozáshoz";
/* No comment provided by engineer. */
"Tap to paste link" = "Koppintson ide a hivatkozás beillesztéséhez";
/* No comment provided by engineer. */
"Tap to scan" = "Koppintson a beolvasáshoz";
"Tap to scan" = "Koppintson ide a QR-kód beolvasáshoz";
/* No comment provided by engineer. */
"TCP connection" = "TCP kapcsolat";
/* No comment provided by engineer. */
"TCP connection timeout" = "TCP kapcsolat időtúllépés";
"TCP connection timeout" = "TCP kapcsolat időtúllépése";
/* No comment provided by engineer. */
"TCP_KEEPCNT" = "TCP_KEEPCNT";
@@ -4464,7 +4500,7 @@
"Thanks to the users contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblate-en!";
/* No comment provided by engineer. */
"The 1st platform without any user identifiers private by design." = "Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre.";
"The 1st platform without any user identifiers private by design." = "Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre.";
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap beállítások megnyitása az engedélyezéshez.";
@@ -4599,7 +4635,7 @@
"To make a new connection" = "Új kapcsolat létrehozásához";
/* No comment provided by engineer. */
"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.";
"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.";
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak.";
@@ -4638,10 +4674,10 @@
"Toolbar opacity" = "Eszköztár átlátszatlansága";
/* No comment provided by engineer. */
"Total" = "Összesen";
"Total" = "Összes kapcsolat";
/* No comment provided by engineer. */
"Transport isolation" = "Kapcsolat izolációs mód";
"Transport isolation" = "Átvitel-izoláció módja";
/* No comment provided by engineer. */
"Transport sessions" = "Munkamenetek átvitele";
@@ -5115,13 +5151,13 @@
"You can enable later via Settings" = "Később engedélyezheti a „Beállításokban”";
/* No comment provided by engineer. */
"You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben.";
"You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben.";
/* No comment provided by engineer. */
"You can give another try." = "Megpróbálhatja még egyszer.";
/* No comment provided by engineer. */
"You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt.";
"You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó -profiljait - csúsztassa jobbra a profilt.";
/* No comment provided by engineer. */
"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”.";
@@ -2,7 +2,7 @@
"CFBundleName" = "SimpleX";
/* Privacy - Camera Usage Description */
"NSCameraUsageDescription" = "A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.";
"NSCameraUsageDescription" = "A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.";
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez";
@@ -11,8 +11,8 @@
"NSLocalNetworkUsageDescription" = "A SimpleX helyi hálózati hozzáférést használ, hogy lehetővé tegye a felhasználói csevegőprofil használatát számítógépen keresztül ugyanazon a hálózaton.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.";
"NSMicrophoneUsageDescription" = "A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez";
"NSPhotoLibraryAddUsageDescription" = "A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez";
+36
View File
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "ID del messaggio errato";
/* No comment provided by engineer. */
"Better calls" = "Chiamate migliorate";
/* No comment provided by engineer. */
"Better groups" = "Gruppi migliorati";
/* No comment provided by engineer. */
"Better message dates." = "Date dei messaggi migliorate.";
/* No comment provided by engineer. */
"Better messages" = "Messaggi migliorati";
/* No comment provided by engineer. */
"Better networking" = "Rete migliorata";
/* No comment provided by engineer. */
"Better notifications" = "Notifiche migliorate";
/* No comment provided by engineer. */
"Better security ✅" = "Sicurezza migliorata ✅";
/* No comment provided by engineer. */
"Better user experience" = "Esperienza utente migliorata";
/* No comment provided by engineer. */
"Black" = "Nero";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Tempo personalizzato";
/* No comment provided by engineer. */
"Customizable message shape." = "Forma dei messaggi personalizzabile.";
/* No comment provided by engineer. */
"Customize theme" = "Personalizza il tema";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Eliminare il database vecchio?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Elimina o modera fino a 200 messaggi.";
/* No comment provided by engineer. */
"Delete pending connection?" = "Eliminare la connessione in attesa?";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Inoltrare i messaggi senza file?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Inoltra fino a 20 messaggi alla volta.";
/* No comment provided by engineer. */
"forwarded" = "inoltrato";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Importazione archivio";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Consegna migliorata, utilizzo di traffico ridotto.\nAltri miglioramenti sono in arrivo!";
/* No comment provided by engineer. */
"Improved message delivery" = "Consegna dei messaggi migliorata";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "Invito SimpleX una tantum";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "Protocolli di SimpleX esaminati da Trail of Bits.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Modalità incognito semplificata";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Supporta SimpleX Chat";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Cambia tra audio e video durante la chiamata.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Cambia profilo di chat per inviti una tantum.";
/* No comment provided by engineer. */
"System" = "Sistema";
+6
View File
@@ -2067,6 +2067,12 @@
/* No comment provided by engineer. */
"Messages & files" = "メッセージ & ファイル";
/* 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. */
"Migrating database archive…" = "データベースのアーカイブを移行しています…";
+36
View File
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "Onjuiste bericht-ID";
/* No comment provided by engineer. */
"Better calls" = "Betere gesprekken";
/* No comment provided by engineer. */
"Better groups" = "Betere groepen";
/* No comment provided by engineer. */
"Better message dates." = "Betere datums voor berichten.";
/* No comment provided by engineer. */
"Better messages" = "Betere berichten";
/* No comment provided by engineer. */
"Better networking" = "Beter netwerk";
/* No comment provided by engineer. */
"Better notifications" = "Betere meldingen";
/* No comment provided by engineer. */
"Better security ✅" = "Betere beveiliging ✅";
/* No comment provided by engineer. */
"Better user experience" = "Betere gebruikerservaring";
/* No comment provided by engineer. */
"Black" = "Zwart";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Aangepaste tijd";
/* No comment provided by engineer. */
"Customizable message shape." = "Aanpasbare berichtvorm.";
/* No comment provided by engineer. */
"Customize theme" = "Thema aanpassen";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Oude database verwijderen?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Maximaal 200 berichten verwijderen of modereren.";
/* No comment provided by engineer. */
"Delete pending connection?" = "Wachtende verbinding verwijderen?";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Berichten doorsturen zonder bestanden?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Stuur maximaal 20 berichten tegelijk door.";
/* No comment provided by engineer. */
"forwarded" = "doorgestuurd";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Archief importeren";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Verbeterde levering, minder data gebruik.\nBinnenkort meer verbeteringen!";
/* No comment provided by engineer. */
"Improved message delivery" = "Verbeterde berichtbezorging";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "Eenmalige SimpleX uitnodiging";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "SimpleX-protocollen beoordeeld door Trail of Bits.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Vereenvoudigde incognitomodus";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Ondersteuning van SimpleX Chat";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Wisselen tussen audio en video tijdens het gesprek.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Wijzig chatprofiel voor eenmalige uitnodigingen.";
/* No comment provided by engineer. */
"System" = "Systeem";
+39
View File
@@ -160,6 +160,9 @@
/* notification title */
"%@ wants to connect!" = "%@ хочет соединиться!";
/* format for date separator in chat */
"%@, %@" = "%1$@, %2$@";
/* No comment provided by engineer. */
"%@, %@ and %lld members" = "%@, %@ и %lld членов группы";
@@ -691,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "Ошибка ID сообщения";
/* No comment provided by engineer. */
"Better calls" = "Улучшенные звонки";
/* No comment provided by engineer. */
"Better groups" = "Улучшенные группы";
/* No comment provided by engineer. */
"Better message dates." = "Улучшенные даты сообщений.";
/* No comment provided by engineer. */
"Better messages" = "Улучшенные сообщения";
/* No comment provided by engineer. */
"Better networking" = "Улучшенные сетевые функции";
/* No comment provided by engineer. */
"Better notifications" = "Улучшенные уведомления";
/* No comment provided by engineer. */
"Better security ✅" = "Улучшенная безопасность ✅";
/* No comment provided by engineer. */
"Better user experience" = "Улучшенный интерфейс";
/* No comment provided by engineer. */
"Black" = "Черная";
@@ -1289,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Пользовательское время";
/* No comment provided by engineer. */
"Customizable message shape." = "Настраиваемая форма сообщений.";
/* No comment provided by engineer. */
"Customize theme" = "Настроить тему";
@@ -1479,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Удалить предыдущую версию данных?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "Удаляйте или модерируйте до 200 сообщений.";
/* No comment provided by engineer. */
"Delete pending connection?" = "Удалить ожидаемое соединение?";
@@ -2227,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Переслать сообщения без файлов?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Пересылайте до 20 сообщений за раз.";
/* No comment provided by engineer. */
"forwarded" = "переслано";
@@ -2467,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Импорт архива";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Улучшенная доставка, меньше трафик.";
/* No comment provided by engineer. */
"Improved message delivery" = "Улучшенная доставка сообщений";
@@ -4268,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "SimpleX одноразовая ссылка";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "Аудит SimpleX протоколов от Trail of Bits.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Упрощенный режим Инкогнито";
@@ -4388,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Поддержать SimpleX Chat";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Переключайте звук и видео во время звонка.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Переключайте профиль чата для одноразовых приглашений.";
/* No comment provided by engineer. */
"System" = "Системная";
+36
View File
@@ -694,15 +694,30 @@
/* No comment provided by engineer. */
"Bad message ID" = "Kötü mesaj kimliği";
/* No comment provided by engineer. */
"Better calls" = "Daha iyi aramalar";
/* No comment provided by engineer. */
"Better groups" = "Daha iyi gruplar";
/* No comment provided by engineer. */
"Better message dates." = "Daha iyi mesaj tarihleri.";
/* No comment provided by engineer. */
"Better messages" = "Daha iyi mesajlar";
/* No comment provided by engineer. */
"Better networking" = "Daha iyi ağ oluşturma";
/* No comment provided by engineer. */
"Better notifications" = "Daha iyi bildirimler";
/* No comment provided by engineer. */
"Better security ✅" = "Daha iyi güvenlik ✅";
/* No comment provided by engineer. */
"Better user experience" = "Daha iyi kullanıcı deneyimi";
/* No comment provided by engineer. */
"Black" = "Siyah";
@@ -1292,6 +1307,9 @@
/* No comment provided by engineer. */
"Custom time" = "Özel saat";
/* No comment provided by engineer. */
"Customizable message shape." = "Özelleştirilebilir mesaj şekli.";
/* No comment provided by engineer. */
"Customize theme" = "Renk temalarını kişiselleştir";
@@ -1482,6 +1500,9 @@
/* No comment provided by engineer. */
"Delete old database?" = "Eski veritabanı silinsin mi?";
/* No comment provided by engineer. */
"Delete or moderate up to 200 messages." = "200'e kadar mesajı silin veya düzenleyin.";
/* No comment provided by engineer. */
"Delete pending connection?" = "Bekleyen bağlantı silinsin mi?";
@@ -2230,6 +2251,9 @@
/* alert message */
"Forward messages without files?" = "Mesajlar dosyalar olmadan iletilsin mi ?";
/* No comment provided by engineer. */
"Forward up to 20 messages at once." = "Aynı anda en fazla 20 mesaj iletin.";
/* No comment provided by engineer. */
"forwarded" = "iletildi";
@@ -2470,6 +2494,9 @@
/* No comment provided by engineer. */
"Importing archive" = "Arşiv içe aktarılıyor";
/* No comment provided by engineer. */
"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "İyileştirilmiş teslimat, azaltılmış trafik kullanımı.\nDaha fazla iyileştirme yakında geliyor!";
/* No comment provided by engineer. */
"Improved message delivery" = "İyileştirilmiş mesaj iletimi";
@@ -4271,6 +4298,9 @@
/* simplex link type */
"SimpleX one-time invitation" = "SimpleX tek kullanımlık davet";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "SimpleX protokolleri Trail of Bits tarafından incelenmiştir.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Basitleştirilmiş gizli mod";
@@ -4391,6 +4421,12 @@
/* No comment provided by engineer. */
"Support SimpleX Chat" = "SimpleX Chat'e destek ol";
/* No comment provided by engineer. */
"Switch audio and video during the call." = "Görüşme sırasında ses ve görüntüyü değiştirin.";
/* No comment provided by engineer. */
"Switch chat profile for 1-time invitations." = "Sohbet profilini 1 kerelik davetler için değiştirin.";
/* No comment provided by engineer. */
"System" = "Sistem";
@@ -24,8 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -37,7 +36,9 @@ import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.NtfManager
import chat.simplex.app.model.NtfManager.AcceptCallAction
import chat.simplex.common.helpers.applyAppLocale
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.platform.chatModel
import chat.simplex.common.ui.theme.*
@@ -49,6 +50,7 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import java.lang.ref.WeakReference
import java.util.*
import chat.simplex.common.platform.chatModel as m
class CallActivity: ComponentActivity(), ServiceConnection {
@@ -56,6 +58,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
var boundService: CallService? = null
override fun onCreate(savedInstanceState: Bundle?) {
applyAppLocale(appPrefs.appLanguage)
super.onCreate(savedInstanceState)
callActivity = WeakReference(this)
when (intent?.action) {
@@ -80,6 +83,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
override fun onDestroy() {
super.onDestroy()
(mainActivity.get() ?: this).applyAppLocale(appPrefs.appLanguage)
if (isOnLockScreenNow()) {
lockAfterIncomingCall()
}
@@ -233,7 +237,7 @@ fun CallActivityView() {
}
SimpleXTheme {
var prevCall by remember { mutableStateOf(call) }
KeyChangeEffect(m.activeCall.value) {
KeyChangeEffect(m.activeCall.value, remember { appPrefs.appLanguage.state }.value) {
if (m.activeCall.value != null) {
prevCall = m.activeCall.value
activity.boundService?.updateNotification()
@@ -31,8 +31,7 @@ private fun Activity.applyLocale(locale: Locale) {
Locale.setDefault(locale)
val appConf = Configuration(androidAppContext.resources.configuration).apply { setLocale(locale) }
val activityConf = Configuration(resources.configuration).apply { setLocale(locale) }
@Suppress("DEPRECATION")
androidAppContext.resources.updateConfiguration(appConf, resources.displayMetrics)
androidAppContext = androidAppContext.createConfigurationContext(appConf)
@Suppress("DEPRECATION")
resources.updateConfiguration(activityConf, resources.displayMetrics)
}
@@ -41,8 +41,10 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.*
import androidx.webkit.WebViewAssetLoader
import androidx.webkit.WebViewClientCompat
import chat.simplex.common.helpers.applyAppLocale
import chat.simplex.common.helpers.showAllowPermissionInSettingsAlert
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
@@ -192,7 +194,11 @@ actual fun ActiveCallView() {
updateActiveCall(call) {
val sources = it.localMediaSources
when (cmd.source) {
CallMediaSource.Mic -> it.copy(localMediaSources = sources.copy(mic = cmd.enable))
CallMediaSource.Mic -> {
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.isMicrophoneMute = !cmd.enable
it.copy(localMediaSources = sources.copy(mic = cmd.enable))
}
CallMediaSource.Camera -> it.copy(localMediaSources = sources.copy(camera = cmd.enable))
CallMediaSource.ScreenAudio -> it.copy(localMediaSources = sources.copy(screenAudio = cmd.enable))
CallMediaSource.ScreenVideo -> it.copy(localMediaSources = sources.copy(screenVideo = cmd.enable))
@@ -702,9 +708,10 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
Box(Modifier.fillMaxSize()) {
AndroidView(
factory = { AndroidViewContext ->
factory = {
try {
(staticWebView ?: WebView(androidAppContext)).apply {
reapplyLocale()
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -775,6 +782,16 @@ private fun updateActiveCall(initial: Call, transform: (Call) -> Call) {
}
}
/*
* Creating WebView automatically drops user's custom app locale to default system locale.
* Preventing it by re-applying custom locale
* https://issuetracker.google.com/issues/109833940
* */
private fun reapplyLocale() {
mainActivity.get()?.applyAppLocale(appPrefs.appLanguage)
callActivity.get()?.applyAppLocale(appPrefs.appLanguage)
}
private class LocalContentWebViewClient(val webView: MutableState<WebView?>, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
override fun shouldInterceptRequest(
view: WebView,
@@ -1889,6 +1889,7 @@ class PendingContactConnection(
@Serializable
enum class ConnStatus {
@SerialName("new") New,
@SerialName("prepared") Prepared,
@SerialName("joined") Joined,
@SerialName("requested") Requested,
@SerialName("accepted") Accepted,
@@ -1898,6 +1899,7 @@ enum class ConnStatus {
val initiated: Boolean? get() = when (this) {
New -> true
Prepared -> false
Joined -> false
Requested -> true
Accepted -> true
@@ -1226,6 +1226,15 @@ object ChatController {
)
return null
}
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
&& r.chatError.agentError is AgentErrorType.SMP
&& r.chatError.agentError.smpErr is SMPErrorType.QUOTA -> {
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.connection_error_quota),
generalGetString(MR.strings.connection_error_quota_desc)
)
return null
}
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r)
@@ -6045,6 +6054,7 @@ sealed class SMPErrorType {
is AUTH -> "AUTH"
is CRYPTO -> "CRYPTO"
is QUOTA -> "QUOTA"
is STORE -> "STORE ${storeErr}"
is NO_MSG -> "NO_MSG"
is LARGE_MSG -> "LARGE_MSG"
is EXPIRED -> "EXPIRED"
@@ -6057,6 +6067,7 @@ sealed class SMPErrorType {
@Serializable @SerialName("AUTH") class AUTH: SMPErrorType()
@Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType()
@Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType()
@Serializable @SerialName("STORE") class STORE(val storeErr: String): SMPErrorType()
@Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType()
@Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType()
@Serializable @SerialName("EXPIRED") class EXPIRED: SMPErrorType()
@@ -144,6 +144,8 @@
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Please check that you used the correct link or ask your contact to send you another one.</string>
<string name="connection_error_auth">Connection error (AUTH)</string>
<string name="connection_error_auth_desc">Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection.</string>
<string name="connection_error_quota">Undelivered messages</string>
<string name="connection_error_quota_desc">The connection reached the limit of undelivered messages, your contact may be offline.</string>
<string name="error_accepting_contact_request">Error accepting contact request</string>
<string name="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
<string name="error_deleting_contact">Error deleting contact</string>
@@ -667,8 +667,8 @@
<string name="callstate_received_confirmation">obdržel potvrzení…</string>
<string name="callstate_connecting">připojování…</string>
<string name="privacy_redefined">Nové vymezení soukromí</string>
<string name="first_platform_without_user_ids">1. platforma bez jakýchkoliv uživatelských identifikátorů soukromá již od návrhu.</string>
<string name="immune_to_spam_and_abuse">Odolná vůči spamu a zneužití</string>
<string name="first_platform_without_user_ids">Bez uživatelských identifikátorů</string>
<string name="immune_to_spam_and_abuse">Odolná vůči spamu</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">K ochraně soukromí, místo uživatelských ID užívaných všemi ostatními platformami, SimpleX používá identifikátory pro fronty zpráv, zvlášť pro každý z vašich kontaktů.</string>
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Mnoho lidí se ptá: <i>když SimpleX nemá žádný identifikátor uživatelů, jak může doručovat zprávy\?</i>]]></string>
<string name="you_control_servers_to_receive_your_contacts_to_send"><![CDATA[Vy určujete, přes které servery <b>přijímat</b> zprávy, vaše kontakty servery, které používáte k zasílání zpráv.]]></string>
@@ -679,8 +679,8 @@
<string name="onboarding_notifications_mode_off">Když aplikace běží</string>
<string name="onboarding_notifications_mode_service">Okamžité</string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Nejlepší pro baterii</b>. Budete přijímat oznámení pouze když aplikace běží (žádná služba na pozadí).]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Dobré pro baterii</b>. Služba na pozadí bude kontrolovat každých 10 minut. Můžete zmeškat hovory nebo naléhavé zprávy.]]></string>
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Využívá více baterie</b>! Služba na pozadí je spuštěna vždy - oznámení se zobrazí, jakmile jsou zprávy k dispozici.]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Dobré pro baterii</b>. Apka bude kontrolovat zprávy každých 10 minut. Můžete zmeškat volání nebo naléhavé zprávy.]]></string>
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Využívá více baterie</b>! Apka stále běží na pozadí - oznámení se zobrazí okamžitě.]]></string>
<string name="paste_the_link_you_received">Vložte přijatý odkaz</string>
<string name="incoming_video_call">Příchozí videohovor</string>
<string name="incoming_audio_call">Příchozí zvukový hovor</string>
@@ -1582,7 +1582,7 @@
<string name="profile_update_event_member_name_changed">člen %1$s změněn na %2$s</string>
<string name="member_info_member_blocked">blokováno</string>
<string name="member_blocked_by_admin">Blokováno adminem</string>
<string name="share_text_created_at">Vytvořeno v: %s</string>
<string name="share_text_created_at">Vytvořen v: %s</string>
<string name="message_too_large">Zpráva příliš velká</string>
<string name="remote_host_disconnected_from"><![CDATA[Odpojeno z mobilu <b>%s</b> z důvodu: %s]]></string>
<string name="remote_host_was_disconnected_title">Spojení zastaveno</string>
@@ -1817,8 +1817,7 @@
<string name="v5_8_safe_files">Bezpečné přijímání souborů</string>
<string name="v5_8_chat_themes">Nové motivy chatu</string>
<string name="v5_8_private_routing">Soukromé směrování zpráv 🚀</string>
<string name="v5_8_private_routing_descr">Chraňte vaši IP adresu před relé zpráv, které jste si vybrali.
\nPovolit v nastavení *Síť &amp; servery*.</string>
<string name="v5_8_private_routing_descr">Chraňte vaši IP adresu před relé zpráv vašich kontaktů.\nPovolte v nastavení *Síť &amp; servery*.</string>
<string name="v5_8_message_delivery">Vylepšené doručování zpráv</string>
<string name="v5_8_persian_ui">Perské UI</string>
<string name="remote_ctrl_connection_stopped_desc">Prosím zkontrolujte, že mobil a desktop jsou připojeny ke stejné místní síti, a že stolní firewall umožňuje připojení.
@@ -1862,4 +1861,193 @@
<string name="cant_call_member_alert_title">Nelze zavolat člena skupiny</string>
<string name="deleted_chats">Archivované kontakty</string>
<string name="v6_0_your_contacts_descr">Archivujte kontakty pro pozdější chatování.</string>
<string name="smp_proxy_error_broker_host">Adresa předávacího serveru je nekompatibilní s nastavením sítě: %1$s.</string>
<string name="smp_proxy_error_broker_version">Verze předávacího serveru je nekompatibilní s nastavením sítě: %1$s.</string>
<string name="proxy_destination_error_broker_host">Cílová adresa serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s.</string>
<string name="smp_proxy_error_connecting">Chyba připojení k přeposílajícímu serveru %1$s. Prosím, zkuste to později.</string>
<string name="proxy_destination_error_failed_to_connect">Předávacímu serveru %1$s se nepodařilo připojit k cílovému serveru %2$s. Prosím, zkuste to později.</string>
<string name="cannot_share_message_alert_text">Vybrané nastavení chatu zakazuje tuto zprávu.</string>
<string name="smp_servers_other">Jiné SMP servery</string>
<string name="smp_servers_configured">Nastavené SMP servery</string>
<string name="servers_info_subscriptions_connections_pending">Probíhá</string>
<string name="chunks_uploaded">Části nahrány</string>
<string name="n_file_errors">%1$d chuba souboru(ů):\n%2$s</string>
<string name="n_other_file_errors">%1$d jiná chyba souboru(ů).</string>
<string name="error_forwarding_messages">Chyba přeposílaní zpráv</string>
<string name="srv_error_host">Adresa serveru není kompatibilní s nastavením sítě.</string>
<string name="forward_alert_title_messages_to_forward">Předat %1$s zpráv(u)?</string>
<string name="forward_alert_title_nothing_to_forward">Nic k předání!</string>
<string name="forward_alert_forward_messages_without_files">Předat zprávy bez souborů?</string>
<string name="forward_files_failed_to_receive_desc">%1$d soubor(y) se nepodařilo stáhnout.</string>
<string name="forward_files_not_accepted_desc">%1$d soubor(y) nestažen(y).</string>
<string name="forward_files_missing_desc">%1$d soubor(y) smazán(y).</string>
<string name="forward_files_messages_deleted_after_selection_title">%1$s zprávy nepředány</string>
<string name="forward_files_not_accepted_receive_files">Stáhnout</string>
<string name="compose_forward_messages_n">Předávám %1$s zpráv</string>
<string name="forward_multiple">Předat zprávy…</string>
<string name="compose_save_messages_n">Uložit %1$s zpráv</string>
<string name="network_proxy_auth_mode_no_auth">Nepoužívat autorizaci s proxy.</string>
<string name="network_proxy_incorrect_config_title">Chyba ukládání proxy</string>
<string name="network_proxy_incorrect_config_desc">Ujistěte se, že nastavení proxy je správné.</string>
<string name="network_proxy_password">Heslo</string>
<string name="network_proxy_auth">Proxy autentizace</string>
<string name="app_check_for_updates_button_install">Instalovat aktualizace</string>
<string name="app_check_for_updates_button_open">Otevřít umístění souboru</string>
<string name="app_check_for_updates_download_started">Stahování aktualizace, nezavírejte aplikaci</string>
<string name="app_check_for_updates_installed_successfully_desc">Prosím restartujte aplikaci.</string>
<string name="app_check_for_updates_installed_successfully_title">Instalovány úspěšně</string>
<string name="app_check_for_updates_button_remind_later">Připomenout později</string>
<string name="app_check_for_updates_notice_title">Zkontrolovat aktualizace</string>
<string name="privacy_media_blur_radius_off">Vypnuto</string>
<string name="settings_section_title_chat_database">CHAT DATABÁZE</string>
<string name="member_info_member_disabled">vypnut</string>
<string name="message_queue_info_server_info">info fronty serveru: %1$s\n\nposlední obdržená zpráva: %2$s</string>
<string name="network_options_save_and_reconnect">Uložit a připojit znovu</string>
<string name="v6_0_chat_list_media">Hrajte ze seznamu chatů.</string>
<string name="servers_info_messages_received">Přijatých zprávy</string>
<string name="servers_info_reconnect_servers_title">Znovu připojit servery?</string>
<string name="completed">Kompletní</string>
<string name="secured">Zabezpečeno</string>
<string name="error_parsing_uri_desc">Prosím zkontrolujte, že SimpleX odkaz je správný.</string>
<string name="error_parsing_uri_title">Chybný odkaz</string>
<string name="proxy_destination_error_broker_version">Verze cílového serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s.</string>
<string name="message_forwarded_title">Zpráva předána</string>
<string name="keep_conversation">Udržujte konverzaci</string>
<string name="only_delete_conversation">Jen smazat konverzaci</string>
<string name="confirm_delete_contact_question">Potvrdit smazání kontaktu?</string>
<string name="delete_contact_cannot_undo_warning">Kontakt bude smazán - nelze vrátit!</string>
<string name="conversation_deleted">Konverzace odstraněna!</string>
<string name="paste_link">Vložit odkaz</string>
<string name="chat_database_exported_title">Chat databáze exportována</string>
<string name="cant_send_message_to_member_alert_title">Členu skupiny nelze odeslat zprávu</string>
<string name="calls_prohibited_ask_to_enable_calls_alert_text">Požádejte váš kontakt ať povolí volání.</string>
<string name="color_sent_quote">Odeslaných odpovědí</string>
<string name="wallpaper_scale">Škálovat</string>
<string name="wallpaper_scale_fit">Přizpůsobit</string>
<string name="v6_0_privacy_blur">Rozmazání pro lepší soukromí.</string>
<string name="v6_0_connect_faster_descr">Připojte se k vašim přátelům rychleji.</string>
<string name="v6_0_delete_many_messages_descr">Smazat až 20 zpráv najednou.</string>
<string name="v6_0_increase_font_size">Zvětšit velikost písma.</string>
<string name="v6_0_connection_servers_status_descr">Stav připojení a serverů.</string>
<string name="v6_0_connection_servers_status">Kontrolujte svou síť</string>
<string name="servers_info_sessions_errors">Chyby</string>
<string name="servers_info_sessions_connected">Připojen</string>
<string name="servers_info_sessions_connecting">Připojování</string>
<string name="servers_info_connected_servers_section_header">Připojené servery</string>
<string name="servers_info_previously_connected_servers_section_header">Dříve připojené servery</string>
<string name="acknowledged">Potvrzeno</string>
<string name="duplicates_label">duplikáty</string>
<string name="deleted">Smazán</string>
<string name="open_server_settings_button">Otevřít nastavení serveru</string>
<string name="v6_0_new_chat_experience">Nový zážitek z chatu 🎉</string>
<string name="v6_0_new_media_options">Nové možnosti médií</string>
<string name="new_message">Nová zpráva</string>
<string name="scan_paste_link">Skenovat / Vložit odkaz</string>
<string name="no_filtered_contacts">Žádné filtrované kontakty</string>
<string name="error_initializing_web_view_wrong_arch">Chyba inicializace WebView. Ujistěte se, že máte nainstalován WebView podporující architekturu arm64.\nChyba: %s</string>
<string name="v6_0_private_routing_descr">Chrání vaši IP adresu a připojení.</string>
<string name="forward_files_messages_deleted_after_selection_desc">Zprávy byly odstraněny poté, co jste je vybrali.</string>
<string name="network_session_mode_session_description">Nové přihlašovací údaje SOCKS budou použity pokaždé, když zapnete aplikaci.</string>
<string name="network_session_mode_server_description">Nové přihlašovací údaje SOCKS budou použity pro každý server.</string>
<string name="servers_info_reconnect_servers_message">Znovu připojte všechny připojené servery pro vynucení doručení. Využívá další provoz.</string>
<string name="reset_all_hints">Resetovat všechny tipy</string>
<string name="forward_files_in_progress_desc">%1$d soubor(y) stále stahuji.</string>
<string name="v6_1_message_dates_descr">Lepší datování zpráv.</string>
<string name="v6_1_better_security">Lepší zabezpečení ✅</string>
<string name="chunks_deleted">Části odstraněny</string>
<string name="info_view_connect_button">připojení</string>
<string name="current_user">Aktuální profil</string>
<string name="servers_info_reconnect_server_error">Chyba znovu připojení serveru</string>
<string name="servers_info_reconnect_servers_error">Chyba při opětovném připojování serverů</string>
<string name="servers_info_reconnect_server_message">Znovu připojte server pro vynucení doručení. Využívá další provoz.</string>
<string name="servers_info_modal_error_title">Chyba</string>
<string name="delete_messages_cannot_be_undone_warning">Zprávy budou smazány - nelze vrátit!</string>
<string name="delete_without_notification">Smazat bez upozornění</string>
<string name="info_view_search_button">hledat</string>
<string name="switching_profile_error_title">Chyba přepínání profilu</string>
<string name="select_chat_profile">Vyberte chat profil</string>
<string name="info_view_message_button">zpráva</string>
<string name="info_view_open_button">otevřít</string>
<string name="contact_deleted">Kontakt smazán!</string>
<string name="member_info_member_inactive">neaktivní</string>
<string name="servers_info_details">Detaily</string>
<string name="servers_info_reset_stats">Resetovat všechny statistiky</string>
<string name="please_try_later">Prosím zkuste později.</string>
<string name="private_routing_error">Chyba soukromého směrování</string>
<string name="network_error_broker_host_desc">Adresa serveru není kompatibilní s nastavením sítě: %1$s.</string>
<string name="member_inactive_title">Člen neaktivní</string>
<string name="member_inactive_desc">Zpráva může být doručena později až bude člen aktivní.</string>
<string name="message_forwarded_desc">Zatím bez přímého spojení, zpráva je předána adminem.</string>
<string name="cant_call_contact_connecting_wait_alert_text">Připojování ke kontaktu, počkejte nebo se podívejte později!</string>
<string name="cant_call_contact_deleted_alert_text">Kontakt odstraněn.</string>
<string name="deletion_errors">Chyby mazání</string>
<string name="servers_info_detailed_statistics">Podrobné statistiky</string>
<string name="chunks_downloaded">Části staženy</string>
<string name="network_session_mode_server">Server</string>
<string name="network_smp_proxy_fallback_allow_protected_description">Odesílat zprávy přímo, když je IP adresa chráněna a váš nebo cílový server nepodporuje soukromé směrování.</string>
<string name="network_smp_proxy_fallback_allow_description">Odeslat zprávy přímo, když váš nebo cílový server nepodporuje soukromé směrování.</string>
<string name="app_check_for_updates">Zkontrolovat aktualizace</string>
<string name="app_check_for_updates_notice_disable">Vypnout</string>
<string name="app_check_for_updates_disabled">Vypnut</string>
<string name="app_check_for_updates_button_download">Stáhnout %s (%s)</string>
<string name="invite_friends_short">Pozvat</string>
<string name="create_address_button">Vytvořit</string>
<string name="settings_message_shape_corner">Roh</string>
<string name="settings_section_title_message_shape">Tvar zpráv</string>
<string name="chat_database_exported_continue">Pokračovat</string>
<string name="call_desktop_permission_denied_chrome">Klikněte na info tlačítko blízko pole adresy, pro použití mikrofonu.</string>
<string name="call_desktop_permission_denied_safari">Otevřete nastavení Safari / Webové stránky / mikrofon, vyberte možnost Povolit pro localhost.</string>
<string name="appearance_font_size">Velikost písma</string>
<string name="v6_0_upgrade_app_descr">Stáhnout nové verze z GitHubu.</string>
<string name="v6_0_reachable_chat_toolbar">Dosažitelný panel nástrojů chatu</string>
<string name="migrate_from_device_remove_archive_question">Odebrat archiv?</string>
<string name="servers_info_files_tab">Soubory</string>
<string name="servers_info_messages_sent">Odeslaných zpráv</string>
<string name="servers_info_downloaded">Staženo</string>
<string name="servers_info_reconnect_server_title">Znovu připojit server?</string>
<string name="decryption_errors">chyba dešifrování</string>
<string name="v6_1_better_calls">Lepší volání</string>
<string name="v6_1_better_user_experience">Větší přívětivost</string>
<string name="v6_1_customizable_message_descr">Přizpůsobitelný tvar zpráv.</string>
<string name="v6_1_delete_many_messages_descr">Smazat nebo moderovat až 200 zpráv.</string>
<string name="v6_1_forward_many_messages_descr">Předat až 20 zpráv najednou.</string>
<string name="servers_info_missing">Žádné info, zkuste načíst znovu</string>
<string name="servers_info">Informace o serverech</string>
<string name="downloaded_files">Stažené soubory</string>
<string name="download_errors">Chyby stahování</string>
<string name="servers_info_reset_stats_alert_error_title">Chyba resetování statistik</string>
<string name="servers_info_detailed_statistics_received_messages_header">Přijaté zprávy</string>
<string name="servers_info_detailed_statistics_received_total">Přijato celkem</string>
<string name="servers_info_detailed_statistics_receive_errors">Chyb přijmutí</string>
<string name="servers_info_reconnect_all_servers_button">Připojte znovu všechny servery</string>
<string name="servers_info_reset_stats_alert_confirm">Reset</string>
<string name="servers_info_reset_stats_alert_title">Resetovat všechny statistiky?</string>
<string name="servers_info_detailed_statistics_sent_messages_header">Odeslané zprávy</string>
<string name="servers_info_detailed_statistics_sent_messages_total">Odeslaných celkem</string>
<string name="server_address">Adresa serveru</string>
<string name="one_hand_ui">Dosažitelný panel nástrojů chatu</string>
<string name="acknowledgement_errors">Chyba potvrzení</string>
<string name="connections">Připojení</string>
<string name="created">Vytvořen</string>
<string name="expired_label">prošlý</string>
<string name="other_label">jiné</string>
<string name="other_errors">jiné chyby</string>
<string name="reconnect">Znovu připojit</string>
<string name="send_errors">Chyby odesílání</string>
<string name="sent_directly">Odesláno přímo</string>
<string name="sent_via_proxy">Odeslaných přes proxy</string>
<string name="delete_members_messages__question">Odstranit %d zpráv členů?</string>
<string name="delete_messages_mark_deleted_warning">Zprávy budou označeny pro smazání. Příjemci budou moci tyto zprávy odhalit.</string>
<string name="select_verb">Vybrat</string>
<string name="compose_message_placeholder">Zpráva</string>
<string name="selected_chat_items_nothing_selected">Nic nevybráno</string>
<string name="selected_chat_items_selected_n">Vybrány %d</string>
<string name="privacy_media_blur_radius_medium">Střední</string>
<string name="servers_info_subscriptions_section_header">Příjem zpráv</string>
<string name="xftp_servers_configured">Nastavené XFTP servery</string>
<string name="media_and_file_servers">Servery médií a souborů</string>
<string name="message_servers">Servery zpráv</string>
<string name="xftp_servers_other">Jiné FXTP servery</string>
<string name="action_button_add_members">Pozvat</string>
<string name="cant_call_member_send_message_alert_text">Pošlete zprávu pro povolení volání.</string>
</resources>
@@ -2119,13 +2119,13 @@
<string name="network_session_mode_server_description">Se usarán credenciales SOCKS nuevas por cada servidor.</string>
<string name="network_session_mode_server">Servidor</string>
<string name="v6_1_better_calls">Llamadas mejoradas</string>
<string name="v6_1_message_dates_descr">Mensajes mejor datados.</string>
<string name="v6_1_message_dates_descr">Sistema de fechas mejorado.</string>
<string name="v6_1_better_user_experience">Experiencia de usuario mejorada</string>
<string name="v6_1_customizable_message_descr">Forma personalizable de los mensajes.</string>
<string name="v6_1_forward_many_messages_descr">Desplazamiento de hasta 20 mensajes.</string>
<string name="v6_1_better_security_descr">Protocolos de SimpleX auditados por Trail of Bits.</string>
<string name="v6_1_better_calls_descr">Intercambia audio y video durante la llamada.</string>
<string name="v6_1_better_security">Seguridad mejorada ✅</string>
<string name="v6_1_delete_many_messages_descr">Borrar o moderar hasta 200 mensajes.</string>
<string name="v6_1_delete_many_messages_descr">Borra o modera hasta 200 mensajes a la vez.</string>
<string name="v6_1_switch_chat_profile_descr">Cambia el perfil de chat para invitaciones de un solo uso.</string>
</resources>
@@ -49,7 +49,7 @@
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Megjegyzés:</b> az üzenet- és fájlközvetítő-kiszolgálók SOCKS proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének elküldése közvetlen kapcsolatot használnak.]]></string>
<string name="full_backup">Alkalmazásadatok biztonsági mentése</string>
<string name="database_initialization_error_title">Az adatbázis előkészítése sikertelen</string>
<string name="all_your_contacts_will_remain_connected_update_sent">Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.</string>
<string name="all_your_contacts_will_remain_connected_update_sent">Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél.</string>
<string name="v4_5_transport_isolation_descr">A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA).</string>
<string name="connect__a_new_random_profile_will_be_shared">Egy új véletlenszerű profil lesz megosztva.</string>
<string name="allow_voice_messages_only_if">A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi.</string>
@@ -219,7 +219,7 @@
<string name="connecting_to_desktop">Kapcsolódás a számítógéphez</string>
<string name="network_session_mode_entity">Kapcsolat</string>
<string name="correct_name_to">Név helyesbítése erre: %s?</string>
<string name="connection_timeout">Kapcsolat időtúllépés</string>
<string name="connection_timeout">Időtúllépés kapcsolódáskor</string>
<string name="connect_with_contact_name_question">Kapcsolódás %1$s által?</string>
<string name="create_profile_button">Létrehozás</string>
<string name="contact_preferences">Ismerős beállításai</string>
@@ -357,7 +357,7 @@
<string name="database_encryption_will_be_updated_in_settings">Adatbázis titkosítási jelmondat frissül és eltárolásra kerül a beállításokban.</string>
<string name="info_row_database_id">Adatbázis-azonosító</string>
<string name="share_text_database_id">Adatbázis-azonosító: %d</string>
<string name="developer_options">Adatbázis-azonosítók és átviteli izolációs beállítások.</string>
<string name="developer_options">Adatbázis-azonosítók és átvitel-izolációs beállítások.</string>
<string name="database_encryption_will_be_updated">Az adatbázis-titkosítási jelmondat megváltoztatásra és mentésre kerül a Keystore-ban.</string>
<string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban.</string>
<string name="smp_servers_delete_server">Kiszolgáló törlése</string>
@@ -504,7 +504,7 @@
<string name="error_starting_chat">Hiba a csevegés elindításakor</string>
<string name="group_profile_is_stored_on_members_devices">A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon.</string>
<string name="enter_passphrase">Jelmondat megadása…</string>
<string name="error_updating_user_privacy">Hiba a felhasználói beállítások frissítésekor</string>
<string name="error_updating_user_privacy">Hiba a felhasználói adatvédelem frissítésekor</string>
<string name="encrypt_database">Titkosít</string>
<string name="alert_title_no_group">Csoport nem található!</string>
<string name="error_saving_smp_servers">Hiba az SMP-kiszolgálók mentésekor</string>
@@ -542,7 +542,7 @@
<string name="files_and_media">Fájlok és médiatartalmak</string>
<string name="section_title_for_console">KONZOLHOZ</string>
<string name="alert_text_encryption_renegotiation_failed">Sikertelen titkosítás-újraegyeztetés.</string>
<string name="error_deleting_user">Hiba a felhasználói profil törlésekor</string>
<string name="error_deleting_user">Hiba a felhasználó-profil törlésekor</string>
<string name="fix_connection_not_supported_by_group_member">Csoporttag általi javítás nem támogatott</string>
<string name="enter_welcome_message">Üdvözlőüzenet megadása…</string>
<string name="encrypted_database">Titkosított adatbázis</string>
@@ -642,7 +642,7 @@
<string name="onboarding_notifications_mode_service">Azonnali</string>
<string name="v5_4_incognito_groups">Inkognitócsoportok</string>
<string name="how_to">Hogyan</string>
<string name="hide_verb">Elrejtés</string>
<string name="hide_verb">Összecsukás</string>
<string name="gallery_image_button">Kép</string>
<string name="v4_3_improved_privacy_and_security">Fejlesztett adatvédelem és biztonság</string>
<string name="ignore">Mellőzés</string>
@@ -677,7 +677,7 @@
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel"><![CDATA[Ha nem tud személyesen találkozni, <b>mutassa meg a QR-kódot a videohívásban</b>, vagy ossza meg a hivatkozást.]]></string>
<string name="network_disable_socks_info">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 kapcsolódik.</string>
<string name="image_will_be_received_when_contact_completes_uploading">A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 számítógép: a megjelenített QR-kód beolvasása az alkalmazásból, a <b>QR kód beolvasásával</b>.]]></string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 számítógép: a megjelenített QR-kód beolvasása az alkalmazásból, a <b>QR-kód beolvasásával</b>.]]></string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">A kapott SimpleX Chat-meghívó-hivatkozását megnyithatja a böngészőjében:</string>
<string name="if_you_enter_self_destruct_code">Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:</string>
<string name="found_desktop">Megtalált számítógép</string>
@@ -686,7 +686,7 @@
<string name="create_chat_profile">Csevegési profil létrehozása</string>
<string name="immune_to_spam_and_abuse">Levélszemét elleni védelem</string>
<string name="disconnect_remote_hosts">Hordozható eszközök leválasztása</string>
<string name="v4_5_multiple_chat_profiles_descr">Különböző nevek, avatarok és átviteli izoláció.</string>
<string name="v4_5_multiple_chat_profiles_descr">Különböző nevek, profilképek és átvitel-izoláció.</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Elutasítás esetén a feladó NEM kap értesítést.</string>
<string name="icon_descr_expand_role">Szerepkörválasztás bővítése</string>
<string name="image_will_be_received_when_contact_is_online">A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!</string>
@@ -801,10 +801,10 @@
<string name="v4_5_multiple_chat_profiles">Több csevegőprofil</string>
<string name="marked_deleted_description">törlésre jelölve</string>
<string name="user_mute">Némítás</string>
<string name="link_a_mobile">Egy hordozható eszköz társítása</string>
<string name="link_a_mobile">Hordozható eszköz társítása</string>
<string name="settings_notifications_mode_title">Értesítési szolgáltatás</string>
<string name="only_group_owners_can_enable_voice">Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését.</string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Csak az alkalmazások tárolják a felhasználói profilokat, ismerősöket, csoportokat és a <b>2 rétegű végpontok közötti titkosítással</b> küldött üzeneteket.]]></string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Csak az alkalmazások tárolják a felhasználó-profilokat, ismerősöket, csoportokat és a <b>2 rétegű végpontok közötti titkosítással</b> küldött üzeneteket.]]></string>
<string name="invalid_migration_confirmation">Érvénytelen átköltöztetési visszaigazolás</string>
<string name="only_group_owners_can_change_prefs">Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat.</string>
<string name="no_history">Nincsenek előzmények</string>
@@ -822,7 +822,7 @@
<string name="feature_offered_item">ajánlott %s</string>
<string name="button_leave_group">Csoport elhagyása</string>
<string name="unblock_member_desc">Minden %s által írt üzenet megjelenik!</string>
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Sokan kérdezték: <i>Ha a SimpleX Chatnek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?</i>]]></string>
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Sokan kérdezték: <i>Ha a SimpleX Chatnek nincs felhasználó-azonosítója, hogyan lehet mégis üzeneteket küldeni?</i>]]></string>
<string name="alert_text_skipped_messages_it_can_happen_when">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 Ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült.</string>
<string name="group_member_role_observer">megfigyelő</string>
<string name="description_via_group_link_incognito">inkognitó a csoporthivatkozáson keresztül</string>
@@ -893,13 +893,13 @@
<string name="add_contact_or_create_group">Új csevegés kezdése</string>
<string name="opensource_protocol_and_code_anybody_can_run_servers">Bárki üzemeltethet kiszolgálókat.</string>
<string name="rcv_group_event_open_chat">Megnyitás</string>
<string name="network_option_protocol_timeout">Protokoll időtúllépés</string>
<string name="network_option_protocol_timeout">Protokoll időtúllépése</string>
<string name="secret_text">titkos</string>
<string name="settings_notification_preview_mode_title">Értesítés előnézete</string>
<string name="callstate_waiting_for_confirmation">várakozás a visszaigazolásra…</string>
<string name="stop_file__action">Fájl megállítása</string>
<string name="description_via_group_link">a csoporthivatkozáson keresztül</string>
<string name="network_option_ping_interval">PING időköze</string>
<string name="network_option_ping_interval">Időtartam a PING-ek között</string>
<string name="send_disappearing_message">Eltűnő üzenet küldése</string>
<string name="self_destruct_passcode">Önmegsemmisítési jelkód</string>
<string name="save_and_update_group_profile">Mentés és a csoportprofil frissítése</string>
@@ -979,12 +979,12 @@
<string name="reject_contact_button">Elutasítás</string>
<string name="notification_preview_mode_message_desc">Ismerős nevének és az üzenet tartalmának megjelenítése</string>
<string name="settings_section_title_settings">BEÁLLÍTÁSOK</string>
<string name="save_profile_password">Felhasználói fiók jelszavának mentése</string>
<string name="save_profile_password">Profiljelszó mentése</string>
<string name="stop_snd_file__title">Fájlküldés megállítása?</string>
<string name="unlink_desktop_question">Számítógép leválasztása?</string>
<string name="voice_messages_prohibited">A hangüzenetek le vannak tilva!</string>
<string name="compose_send_direct_message_to_connect">Közvetlen üzenet küldése a kapcsolódáshoz</string>
<string name="network_option_ping_count">PING számláló</string>
<string name="network_option_ping_count">PING-ek száma</string>
<string name="show_developer_options">Fejlesztői beállítások megjelenítése</string>
<string name="rcv_group_event_1_member_connected">%s kapcsolódott</string>
<string name="theme_system">Rendszer</string>
@@ -1065,7 +1065,7 @@
<string name="onboarding_notifications_mode_title">Privát értesítések</string>
<string name="you_invited_a_contact">Meghívta egy ismerősét</string>
<string name="is_not_verified">%s nincs hitelesítve</string>
<string name="contact_tap_to_connect">Koppintson a kapcsolódáshoz</string>
<string name="contact_tap_to_connect">Koppintson ide a kapcsolódáshoz</string>
<string name="this_device_name">Ennek az eszköznek a neve</string>
<string name="your_current_profile">Jelenlegi profil</string>
<string name="smp_server_test_upload_file">Fájl feltöltése</string>
@@ -1124,7 +1124,7 @@
<string name="settings_section_title_use_from_desktop">Társítás számítógéppel</string>
<string name="settings_section_title_you">PROFIL</string>
<string name="network_proxy_port">port %d</string>
<string name="to_connect_via_link_title">Kapcsolódás hivatkozáson keresztül</string>
<string name="to_connect_via_link_title">Kapcsolódás egy hivatkozáson keresztül</string>
<string name="share_address">Cím megosztása</string>
<string name="smp_servers_scan_qr">A kiszolgáló QR-kódjának beolvasása</string>
<string name="stop_chat_confirmation">Megállítás</string>
@@ -1218,7 +1218,7 @@
<string name="snd_group_event_member_deleted">Ön eltávolította őt: %1$s</string>
<string name="save_passphrase_and_open_chat">Jelmondat mentése és a csevegés megnyitása</string>
<string name="save_preferences_question">Beállítások mentése?</string>
<string name="first_platform_without_user_ids">Nincsenek felhasználói azonosítók.</string>
<string name="first_platform_without_user_ids">Nincsenek felhasználó-azonosítók.</string>
<string name="prohibit_direct_messages">A közvetlen üzenetek küldése a tagok között le van tiltva.</string>
<string name="network_enable_socks">SOCKS proxy használata?</string>
<string name="icon_descr_speaker_off">Hangszóró kikapcsolva</string>
@@ -1279,14 +1279,14 @@
<string name="use_simplex_chat_servers__question">SimpleX Chat-kiszolgálók használata?</string>
<string name="unhide_chat_profile">Csevegési profil felfedése</string>
<string name="v5_0_large_files_support">Videók és fájlok 1Gb méretig</string>
<string name="network_option_tcp_connection_timeout">TCP kapcsolat időtúllépés</string>
<string name="network_option_tcp_connection_timeout">TCP kapcsolat időtúllépése</string>
<string name="connect__your_profile_will_be_shared">A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni.</string>
<string name="you_are_already_connected_to_vName_via_this_link">Ön már kapcsolódva van ehhez: %1$s.</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által!
\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek.</string>
<string name="chat_with_the_founder">Ötletek és javaslatok</string>
<string name="database_downgrade_warning">Figyelmeztetés: néhány adat elveszhet!</string>
<string name="tap_to_start_new_chat">Koppintson az új csevegés indításához</string>
<string name="tap_to_start_new_chat">Koppintson ide az új csevegés indításához</string>
<string name="waiting_for_desktop">Várakozás a számítógépre…</string>
<string name="next_generation_of_private_messaging">A privát üzenetküldés
\nkövetkező generációja</string>
@@ -1311,7 +1311,7 @@
<string name="restore_passphrase_not_found_desc">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ágimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel.</string>
<string name="your_contacts_will_remain_connected">Az ismerősei továbbra is kapcsolódva maradnak.</string>
<string name="error_xftp_test_server_auth">A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát</string>
<string name="database_initialization_error_desc">Az adatbázis nem működik megfelelően. Koppintson további információért</string>
<string name="database_initialization_error_desc">Az adatbázis nem működik megfelelően. Koppintson ide a további információkért</string>
<string name="stop_snd_file__message">A fájl küldése leállt.</string>
<string name="trying_to_connect_to_server_to_receive_messages">Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál.</string>
<string name="la_could_not_be_verified">Nem sikerült hitelesíteni; próbálja meg újra.</string>
@@ -1368,7 +1368,7 @@
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[A csatlakozás már folyamatban van a(z) <b>%1$s</b> nevű csoporthoz.]]></string>
<string name="onboarding_notifications_mode_off">Amikor az alkalmazás fut</string>
<string name="alert_title_cant_invite_contacts_descr">Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva</string>
<string name="v4_5_transport_isolation">Kapcsolat izolációs mód</string>
<string name="v4_5_transport_isolation">Átvitel-izoláció</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később!</string>
<string name="voice_messages_are_prohibited">A hangüzenetek küldése le van tiltva ebben a csoportban.</string>
<string name="system_restricted_background_in_call_warn"><![CDATA[A háttérben való hívásokhoz válassza ki az <b>Alkalmazás akkumulátor-használata</b> / <b>Korlátlan</b> módot az alkalmazás beállításaiban.]]></string>
@@ -1386,11 +1386,11 @@
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">A profilja az eszközén van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját.</string>
<string name="snd_group_event_changed_member_role">Ön megváltoztatta %s szerepkörét erre: %s</string>
<string name="you_rejected_group_invitation">Csoportmeghívó elutasítva</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt.</string>
<string name="to_share_with_your_contact">(a megosztáshoz az ismerősével)</string>
<string name="you_sent_group_invitation">Csoportmeghívó elküldve</string>
<string name="update_network_session_mode_question">Kapcsolat izolációs mód frissítése?</string>
<string name="network_session_mode_transport_isolation">Kapcsolat izolációs mód</string>
<string name="update_network_session_mode_question">Átvitel-izoláció módjának frissítése?</string>
<string name="network_session_mode_transport_isolation">Átvitel-izoláció</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.</string>
<string name="database_is_not_encrypted">A csevegési adatbázis nem titkosított - állítson be egy jelmondatot annak védelméhez.</string>
<string name="network_disable_socks">Közvetlen internet kapcsolat használata?</string>
@@ -1421,7 +1421,7 @@
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Kapcsolatba léphet <font color="#0088ff">a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról</font>.]]></string>
<string name="v4_2_auto_accept_contact_requests_desc">Nem kötelező üdvözlőüzenettel.</string>
<string name="unknown_database_error_with_info">Ismeretlen adatbázishiba: %s</string>
<string name="you_can_hide_or_mute_user_profile">Elrejtheti vagy lenémíthatja a felhasználó profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz.</string>
<string name="you_can_hide_or_mute_user_profile">Elrejtheti vagy lenémíthatja a felhasználó-profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz.</string>
<string name="v5_3_simpler_incognito_mode_descr">Inkognitómód használata kapcsolódáskor.</string>
<string name="you_can_share_group_link_anybody_will_be_able_to_connect">Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait.</string>
<string name="you_joined_this_group">Csatlakozott ehhez a csoporthoz</string>
@@ -1438,12 +1438,12 @@
<string name="v4_6_group_welcome_message_descr">Megjelenítendő üzenet beállítása az új tagok számára!</string>
<string name="whats_new_thanks_to_users_contribute_weblate">Köszönet a felhasználóknak - hozzájárulás a Weblate-en!</string>
<string name="sending_delivery_receipts_will_be_enabled">A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül.</string>
<string name="network_option_protocol_timeout_per_kb">Protokoll időkorlát KB-onként</string>
<string name="network_option_protocol_timeout_per_kb">Protokoll időtúllépése KB-onként</string>
<string name="database_backup_can_be_restored">Az adatbázis-jelmondat megváltoztatására tett kísérlet nem fejeződött be.</string>
<string name="enable_automatic_deletion_message">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.</string>
<string name="profile_is_only_shared_with_your_contacts">A profilja csak az ismerőseivel kerül megosztásra.</string>
<string name="smp_servers_test_some_failed">Néhány kiszolgáló megbukott a teszten:</string>
<string name="group_invitation_tap_to_join">Koppintson a csatlakozáshoz</string>
<string name="group_invitation_tap_to_join">Koppintson ide a csatlakozáshoz</string>
<string name="delete_files_and_media_desc">Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalmakkal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak.</string>
<string name="receipts_contacts_override_enabled">A kézbesítési jelentések engedélyezve vannak %d ismerősnél</string>
<string name="sending_via">Küldés ezen keresztül:</string>
@@ -1457,7 +1457,7 @@
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Az ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!</string>
<string name="you_can_change_it_later">A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban.
\nEz később megváltoztatható.</string>
<string name="group_invitation_tap_to_join_incognito">Koppintson az inkognitóban való kapcsolódáshoz</string>
<string name="group_invitation_tap_to_join_incognito">Koppintson ide az inkognitóban való kapcsolódáshoz</string>
<string name="set_password_to_export">Jelmondat beállítása az exportáláshoz</string>
<string name="receipts_groups_override_disabled">A kézbesítési jelentések le vannak tiltva %d csoportban</string>
<string name="non_fatal_errors_occured_during_import">Néhány nem végzetes hiba történt az importáláskor:</string>
@@ -1519,7 +1519,7 @@
<string name="recent_history">Látható előzmények</string>
<string name="la_app_passcode">Alkalmazás jelkód</string>
<string name="add_contact_tab">Ismerős hozzáadása</string>
<string name="tap_to_scan">Koppintson a beolvasáshoz</string>
<string name="tap_to_scan">Koppintson ide a QR-kód beolvasásához</string>
<string name="tap_to_paste_link">Koppintson ide a hivatkozás beillesztéséhez</string>
<string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Ismerős hozzáadása</b>: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]></string>
<string name="create_group_button_to_create_new_group"><![CDATA[<b>Csoport létrehozása</b>: új csoport létrehozásához.]]></string>
@@ -1768,7 +1768,7 @@
<string name="network_smp_proxy_mode_never">Soha</string>
<string name="network_smp_proxy_mode_unknown">Ismeretlen kiszolgálók</string>
<string name="network_smp_proxy_fallback_allow_protected">Ha az IP-cím rejtett</string>
<string name="private_routing_show_message_status">Üzenet állapot megjelenítése</string>
<string name="private_routing_show_message_status">Üzenetállapot megjelenítése</string>
<string name="network_smp_proxy_fallback_allow_downgrade">Visszafejlesztés engedélyezése</string>
<string name="network_smp_proxy_mode_always">Mindig</string>
<string name="network_smp_proxy_fallback_prohibit">Nem</string>
@@ -1874,8 +1874,7 @@
<string name="servers_info_sessions_connecting">Kapcsolódás</string>
<string name="servers_info_sessions_errors">Hibák</string>
<string name="servers_info_subscriptions_connections_pending">Függőben</string>
<string name="servers_info_private_data_disclaimer">Ekkortól kezdve: %s.
\nMinden adat biztonságban van az eszközén.</string>
<string name="servers_info_private_data_disclaimer">Statisztikagyűjtés kezdete: %s.\nMinden adat biztonságban van az eszközén.</string>
<string name="servers_info_messages_sent">Elküldött üzenetek</string>
<string name="servers_info_proxied_servers_section_header">Proxyzott kiszolgálók</string>
<string name="servers_info_reconnect_servers_title">Újrakapcsolódás a kiszolgálókhoz?</string>
@@ -1891,15 +1890,15 @@
<string name="servers_info_downloaded">Letöltve</string>
<string name="expired_label">lejárt</string>
<string name="other_label">egyéb</string>
<string name="servers_info_detailed_statistics_received_total">Összes fogadott</string>
<string name="servers_info_detailed_statistics_received_total">Összes fogadott üzenet</string>
<string name="servers_info_detailed_statistics_receive_errors">Üzenetfogadási hibák</string>
<string name="reconnect">Újrakapcsolás</string>
<string name="send_errors">Üzenetküldési hibák</string>
<string name="sent_directly">Közvetlenül küldött</string>
<string name="servers_info_detailed_statistics_sent_messages_total">Összes elküldött</string>
<string name="servers_info_detailed_statistics_sent_messages_total">Összes elküldött üzenet</string>
<string name="sent_via_proxy">Proxyn keresztül küldve</string>
<string name="smp_server">SMP-kiszolgáló</string>
<string name="servers_info_starting_from">Ekkortól kezdve: %s.</string>
<string name="servers_info_starting_from">Statisztikagyűjtés kezdete: %s.</string>
<string name="servers_info_uploaded">Feltöltve</string>
<string name="xftp_server">XFTP-kiszolgáló</string>
<string name="proxied">Proxyzott</string>
@@ -1927,7 +1926,7 @@
<string name="xftp_servers_configured">Konfigurált XFTP-kiszolgálók</string>
<string name="servers_info_sessions_connected">Kapcsolódva</string>
<string name="current_user">Jelenlegi profil</string>
<string name="servers_info_details">Részletek</string>
<string name="servers_info_details">További részletek</string>
<string name="decryption_errors">visszafejtési hibák</string>
<string name="deleted">Törölve</string>
<string name="servers_info_messages_received">Fogadott üzenetek</string>
@@ -1944,7 +1943,7 @@
<string name="servers_info_reconnect_server_message">A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ.</string>
<string name="servers_info_detailed_statistics_sent_messages_header">Elküldött üzenetek</string>
<string name="servers_info_transport_sessions_section_header">Munkamenetek átvitele</string>
<string name="servers_info_subscriptions_total">Összesen</string>
<string name="servers_info_subscriptions_total">Összes kapcsolat</string>
<string name="servers_info_statistics_section_header">Statisztikák</string>
<string name="servers_info_target">Információk megjelenítése ehhez:</string>
<string name="network_error_broker_version_desc">A kiszolgáló verziója nem kompatibilis az alkalmazással: %1$s.</string>
@@ -2120,7 +2120,7 @@
<string name="v6_1_message_dates_descr">Betere datums voor berichten.</string>
<string name="v6_1_better_user_experience">Betere gebruikerservaring</string>
<string name="v6_1_customizable_message_descr">Aanpasbare berichtvorm.</string>
<string name="v6_1_better_calls_descr">Wissel tussen audio en video tijdens het gesprek.</string>
<string name="v6_1_better_calls_descr">Wisselen tussen audio en video tijdens het gesprek.</string>
<string name="v6_1_switch_chat_profile_descr">Wijzig chatprofiel voor eenmalige uitnodigingen.</string>
<string name="v6_1_delete_many_messages_descr">Maximaal 200 berichten verwijderen of modereren.</string>
<string name="v6_1_forward_many_messages_descr">Stuur maximaal 20 berichten tegelijk door.</string>
@@ -2198,7 +2198,7 @@
<string name="v6_1_forward_many_messages_descr">Пересылайте до 20 сообщений за раз.</string>
<string name="v6_1_better_calls_descr">Переключайте звук и видео во время звонка.</string>
<string name="v6_1_switch_chat_profile_descr">Переключайте профиль чата для одноразовых приглашений.</string>
<string name="v6_1_better_security_descr">Анализ безопасности протоколов SimpleX от Trail of Bits.</string>
<string name="v6_1_better_security_descr">Аудит SimpleX протоколов от Trail of Bits.</string>
<string name="call_desktop_permission_denied_title">Чтобы совершать звонки, разрешите использовать микрофон. Завершите вызов и попробуйте позвонить снова.</string>
<string name="network_proxy_auth_mode_no_auth">Не использовать учетные данные с прокси.</string>
<string name="network_proxy_incorrect_config_title">Ошибка сохранения прокси</string>
@@ -63,6 +63,7 @@ const allowSendScreenAudio = false;
// When one side of a call sends candidates tot fast (until local & remote descriptions are set), that candidates
// will be stored here and then set when the call will be ready to process them
let afterCallInitializedCandidates = [];
const stopTrackOnAndroid = false;
const processCommand = (function () {
const defaultIceServers = [
{ urls: ["stuns:stun.simplex.im:443"] },
@@ -844,7 +845,10 @@ const processCommand = (function () {
// doing it vice versa gives an error like "too many cameras were open" on some Android devices or webViews
// which means the second camera will never be opened
for (const t of source == CallMediaSource.Mic ? call.localStream.getAudioTracks() : call.localStream.getVideoTracks()) {
t.stop();
if (isDesktop || source != CallMediaSource.Mic || stopTrackOnAndroid)
t.stop();
else
t.enabled = false;
call.localStream.removeTrack(t);
}
let localStream;
@@ -894,7 +898,7 @@ const processCommand = (function () {
if (!localStream || !oldCamera || !videos)
return;
if (!inactiveCallMediaSources.mic) {
localStream.getAudioTracks().forEach((elem) => elem.stop());
localStream.getAudioTracks().forEach((elem) => (isDesktop || stopTrackOnAndroid ? elem.stop() : (elem.enabled = false)));
localStream.getAudioTracks().forEach((elem) => localStream.removeTrack(elem));
}
if (!inactiveCallMediaSources.camera || oldCamera != newCamera) {
@@ -1157,7 +1161,10 @@ const processCommand = (function () {
transceiver.sender.replaceTrack(t);
}
else {
t.stop();
if (isDesktop || t.kind == CallMediaType.Video || stopTrackOnAndroid)
t.stop();
else
t.enabled = false;
s.removeTrack(t);
transceiver.sender.replaceTrack(null);
}
+4 -4
View File
@@ -26,11 +26,11 @@ android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
android.version_name=6.1-beta.4
android.version_code=245
android.version_name=6.1.1
android.version_code=249
desktop.version_name=6.1-beta.4
desktop.version_code=71
desktop.version_name=6.1.1
desktop.version_code=74
kotlin.version=1.9.23
gradle.plugin.version=8.2.0
@@ -106,11 +106,13 @@ data DirectoryCmdTag (r :: DirectoryRole) where
DCConfirmDuplicateGroup_ :: DirectoryCmdTag 'DRUser
DCListUserGroups_ :: DirectoryCmdTag 'DRUser
DCDeleteGroup_ :: DirectoryCmdTag 'DRUser
DCSetRole_ :: DirectoryCmdTag 'DRUser
DCApproveGroup_ :: DirectoryCmdTag 'DRSuperUser
DCRejectGroup_ :: DirectoryCmdTag 'DRSuperUser
DCSuspendGroup_ :: DirectoryCmdTag 'DRSuperUser
DCResumeGroup_ :: DirectoryCmdTag 'DRSuperUser
DCListLastGroups_ :: DirectoryCmdTag 'DRSuperUser
DCListPendingGroups_ :: DirectoryCmdTag 'DRSuperUser
DCExecuteCommand_ :: DirectoryCmdTag 'DRSuperUser
deriving instance Show (DirectoryCmdTag r)
@@ -127,11 +129,13 @@ data DirectoryCmd (r :: DirectoryRole) where
DCConfirmDuplicateGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser
DCListUserGroups :: DirectoryCmd 'DRUser
DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser
DCSetRole :: GroupId -> GroupName -> GroupMemberRole -> DirectoryCmd 'DRUser
DCApproveGroup :: {groupId :: GroupId, displayName :: GroupName, groupApprovalId :: GroupApprovalId} -> DirectoryCmd 'DRSuperUser
DCRejectGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser
DCSuspendGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser
DCResumeGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser
DCListLastGroups :: Int -> DirectoryCmd 'DRSuperUser
DCListPendingGroups :: Int -> DirectoryCmd 'DRSuperUser
DCExecuteCommand :: String -> DirectoryCmd 'DRSuperUser
DCUnknownCommand :: DirectoryCmd 'DRUser
DCCommandError :: DirectoryCmdTag r -> DirectoryCmd r
@@ -163,11 +167,13 @@ directoryCmdP =
"list" -> u DCListUserGroups_
"ls" -> u DCListUserGroups_
"delete" -> u DCDeleteGroup_
"role" -> u DCSetRole_
"approve" -> su DCApproveGroup_
"reject" -> su DCRejectGroup_
"suspend" -> su DCSuspendGroup_
"resume" -> su DCResumeGroup_
"last" -> su DCListLastGroups_
"pending" -> su DCListPendingGroups_
"exec" -> su DCExecuteCommand_
"x" -> su DCExecuteCommand_
_ -> fail "bad command tag"
@@ -184,14 +190,19 @@ directoryCmdP =
DCConfirmDuplicateGroup_ -> gc DCConfirmDuplicateGroup
DCListUserGroups_ -> pure DCListUserGroups
DCDeleteGroup_ -> gc DCDeleteGroup
DCSetRole_ -> do
(groupId, displayName) <- gc (,)
memberRole <- A.space *> ("member" $> GRMember <|> "observer" $> GRObserver)
pure $ DCSetRole groupId displayName memberRole
DCApproveGroup_ -> do
(groupId, displayName) <- gc (,)
groupApprovalId <- A.space *> A.decimal
pure $ DCApproveGroup {groupId, displayName, groupApprovalId}
pure DCApproveGroup {groupId, displayName, groupApprovalId}
DCRejectGroup_ -> gc DCRejectGroup
DCSuspendGroup_ -> gc DCSuspendGroup
DCResumeGroup_ -> gc DCResumeGroup
DCListLastGroups_ -> DCListLastGroups <$> (A.space *> A.decimal <|> pure 10)
DCListPendingGroups_ -> DCListPendingGroups <$> (A.space *> A.decimal <|> pure 10)
DCExecuteCommand_ -> DCExecuteCommand . T.unpack <$> (A.space *> A.takeText)
where
gc f = f <$> (A.space *> A.decimal <* A.char ':') <*> displayNameP
@@ -214,13 +225,15 @@ directoryCmdTag = \case
DCRecentGroups -> "new"
DCSubmitGroup _ -> "submit"
DCConfirmDuplicateGroup {} -> "confirm"
DCListUserGroups -> "list"
DCListUserGroups -> "list"
DCDeleteGroup {} -> "delete"
DCApproveGroup {} -> "approve"
DCSetRole {} -> "role"
DCRejectGroup {} -> "reject"
DCSuspendGroup {} -> "suspend"
DCResumeGroup {} -> "resume"
DCListLastGroups _ -> "last"
DCListPendingGroups _ -> "pending"
DCExecuteCommand _ -> "exec"
DCUnknownCommand -> "unknown"
DCCommandError _ -> "error"
@@ -23,6 +23,7 @@ import Data.Set (Set)
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1)
import Data.Time.Clock (diffUTCTime, getCurrentTime)
import Data.Time.LocalTime (getCurrentTimeZone)
import Directory.Events
@@ -447,30 +448,43 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
DCRecentGroups -> withFoundListedGroups Nothing $ sendAllGroups takeRecent "the most recent" STRecent
DCSubmitGroup _link -> pure ()
DCConfirmDuplicateGroup ugrId gName ->
atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case
Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found"
Just GroupReg {dbGroupId, groupRegStatus} -> do
getGroup cc dbGroupId >>= \case
Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found"
Just g@GroupInfo {groupProfile = GroupProfile {displayName}}
| displayName == gName ->
readTVarIO groupRegStatus >>= \case
GRSPendingConfirmation -> do
getDuplicateGroup g >>= \case
Nothing -> sendMessage cc ct "Error: getDuplicateGroup. Please notify the developers."
Just DGReserved -> sendMessage cc ct $ groupAlreadyListed g
_ -> processInvitation ct g
_ -> sendReply $ "Error: the group ID " <> show ugrId <> " (" <> T.unpack displayName <> ") is not pending confirmation."
| otherwise -> sendReply $ "Group ID " <> show ugrId <> " has the display name " <> T.unpack displayName
withUserGroupReg ugrId gName $ \gr g@GroupInfo {groupProfile = GroupProfile {displayName}} ->
readTVarIO (groupRegStatus gr) >>= \case
GRSPendingConfirmation ->
getDuplicateGroup g >>= \case
Nothing -> sendMessage cc ct "Error: getDuplicateGroup. Please notify the developers."
Just DGReserved -> sendMessage cc ct $ groupAlreadyListed g
_ -> processInvitation ct g
_ -> sendReply $ "Error: the group ID " <> show ugrId <> " (" <> T.unpack displayName <> ") is not pending confirmation."
DCListUserGroups ->
atomically (getUserGroupRegs st $ contactId' ct) >>= \grs -> do
sendReply $ show (length grs) <> " registered group(s)"
void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} ->
sendGroupInfo ct gr userGroupRegId Nothing
DCDeleteGroup _ugrId _gName -> pure ()
DCDeleteGroup ugrId gName ->
withUserGroupReg ugrId gName $ \gr GroupInfo {groupProfile = GroupProfile {displayName}} -> do
delGroupReg st gr
sendReply $ T.unpack $ "Your group " <> displayName <> " is deleted from the directory"
DCSetRole ugrId gName mRole ->
withUserGroupReg ugrId gName $ \_gr GroupInfo {groupId, groupProfile = GroupProfile {displayName}} -> do
gLink_ <- setGroupLinkRole cc groupId mRole
sendReply $ T.unpack $ case gLink_ of
Nothing -> "Error: the initial member role for the group " <> displayName <> " was NOT upgated"
Just gLink ->
("The initial member role for the group " <> displayName <> " is set to *" <> decodeLatin1 (strEncode mRole) <> "*\n\n")
<> ("*Please note*: it applies only to members joining via this link: " <> safeDecodeUtf8 (strEncode $ simplexChatContact gLink))
DCUnknownCommand -> sendReply "Unknown command"
DCCommandError tag -> sendReply $ "Command error: " <> show tag
where
withUserGroupReg ugrId gName action =
atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case
Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found"
Just gr@GroupReg {dbGroupId} -> do
getGroup cc dbGroupId >>= \case
Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found"
Just g@GroupInfo {groupProfile = GroupProfile {displayName}}
| displayName == gName -> action gr g
| otherwise -> sendReply $ "Group ID " <> show ugrId <> " has the display name " <> T.unpack displayName
sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent
withFoundListedGroups s_ action =
getGroups_ s_ >>= \case
@@ -576,13 +590,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
notifyOwner gr $ "The group " <> userGroupReference' gr gName <> " is listed in the directory again!"
sendReply "Group listing resumed!"
_ -> sendReply $ "The group " <> groupRef <> " is not suspended, can't be resumed."
DCListLastGroups count ->
readTVarIO (groupRegs st) >>= \grs -> do
sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "")
void . forkIO $ forM_ (reverse $ take count grs) $ \gr@GroupReg {dbGroupId, dbContactId} -> do
ct_ <- getContact cc dbContactId
let ownerStr = "Owner: " <> maybe "getContact error" localDisplayName' ct_
sendGroupInfo ct gr dbGroupId $ Just ownerStr
DCListLastGroups count -> listGroups count False
DCListPendingGroups count -> listGroups count True
DCExecuteCommand cmdStr ->
sendChatCmdStr cc cmdStr >>= \r -> do
ts <- getCurrentTime
@@ -593,6 +602,17 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
where
superUser = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct}
sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent
listGroups count pending =
readTVarIO (groupRegs st) >>= \groups -> do
grs <-
if pending
then filterM (fmap pendingApproval . readTVarIO . groupRegStatus) groups
else pure groups
sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "")
void . forkIO $ forM_ (reverse $ take count grs) $ \gr@GroupReg {dbGroupId, dbContactId} -> do
ct_ <- getContact cc dbContactId
let ownerStr = "Owner: " <> maybe "getContact error" localDisplayName' ct_
sendGroupInfo ct gr dbGroupId $ Just ownerStr
getGroupAndReg :: GroupId -> GroupName -> IO (Maybe (GroupInfo, GroupReg))
getGroupAndReg gId gName =
@@ -641,5 +661,12 @@ getGroupAndSummary cc gId = resp <$> sendChatCmd cc (APIGroupInfo gId)
CRGroupInfo {groupInfo, groupSummary} -> Just (groupInfo, groupSummary)
_ -> Nothing
setGroupLinkRole :: ChatController -> GroupId -> GroupMemberRole -> IO (Maybe ConnReqContact)
setGroupLinkRole cc gId mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole gId mRole)
where
resp = \case
CRGroupLink _ _ gLink _ -> Just gLink
_ -> Nothing
unexpectedError :: String -> String
unexpectedError err = "Unexpected error: " <> err <> ", please notify the developers."
@@ -12,6 +12,7 @@ module Directory.Store
GroupApprovalId,
restoreDirectoryStore,
addGroupReg,
delGroupReg,
setGroupStatus,
setGroupRegOwner,
getGroupReg,
@@ -19,6 +20,7 @@ module Directory.Store
getUserGroupRegs,
filterListedGroups,
groupRegStatusText,
pendingApproval,
)
where
@@ -79,6 +81,11 @@ data GroupRegStatus
| GRSSuspendedBadRoles
| GRSRemoved
pendingApproval :: GroupRegStatus -> Bool
pendingApproval = \case
GRSPendingApproval _ -> True
_ -> False
data DirectoryStatus = DSListed | DSReserved | DSRegistered
groupRegStatusText :: GroupRegStatus -> Text
@@ -118,6 +125,12 @@ addGroupReg st ct GroupInfo {groupId} grStatus = do
| dbContactId == ctId && userGroupRegId > mx = userGroupRegId
| otherwise = mx
delGroupReg :: DirectoryStore -> GroupReg -> IO ()
delGroupReg st GroupReg {dbGroupId = gId} = do
logGDelete st gId
atomically $ unlistGroup st gId
atomically $ modifyTVar' (groupRegs st) $ filter ((gId ==) . dbGroupId)
setGroupStatus :: DirectoryStore -> GroupReg -> GroupRegStatus -> IO ()
setGroupStatus st gr grStatus = do
logGUpdateStatus st (dbGroupId gr) grStatus
@@ -167,10 +180,15 @@ unlistGroup st gId = do
data DirectoryLogRecord
= GRCreate GroupRegData
| GRDelete GroupId
| GRUpdateStatus GroupId GroupRegStatus
| GRUpdateOwner GroupId GroupMemberId
data DLRTag = GRCreate_ | GRUpdateStatus_ | GRUpdateOwner_
data DLRTag
= GRCreate_
| GRDelete_
| GRUpdateStatus_
| GRUpdateOwner_
logDLR :: DirectoryStore -> DirectoryLogRecord -> IO ()
logDLR st r = forM_ (directoryLogFile st) $ \h -> B.hPutStrLn h (strEncode r)
@@ -178,6 +196,9 @@ logDLR st r = forM_ (directoryLogFile st) $ \h -> B.hPutStrLn h (strEncode r)
logGCreate :: DirectoryStore -> GroupRegData -> IO ()
logGCreate st = logDLR st . GRCreate
logGDelete :: DirectoryStore -> GroupId -> IO ()
logGDelete st = logDLR st . GRDelete
logGUpdateStatus :: DirectoryStore -> GroupId -> GroupRegStatus -> IO ()
logGUpdateStatus st = logDLR st .: GRUpdateStatus
@@ -187,11 +208,13 @@ logGUpdateOwner st = logDLR st .: GRUpdateOwner
instance StrEncoding DLRTag where
strEncode = \case
GRCreate_ -> "GCREATE"
GRDelete_ -> "GDELETE"
GRUpdateStatus_ -> "GSTATUS"
GRUpdateOwner_ -> "GOWNER"
strP =
A.takeTill (== ' ') >>= \case
"GCREATE" -> pure GRCreate_
"GDELETE" -> pure GRDelete_
"GSTATUS" -> pure GRUpdateStatus_
"GOWNER" -> pure GRUpdateOwner_
_ -> fail "invalid DLRTag"
@@ -199,13 +222,15 @@ instance StrEncoding DLRTag where
instance StrEncoding DirectoryLogRecord where
strEncode = \case
GRCreate gr -> strEncode (GRCreate_, gr)
GRDelete gId -> strEncode (GRDelete_, gId)
GRUpdateStatus gId grStatus -> strEncode (GRUpdateStatus_, gId, grStatus)
GRUpdateOwner gId grOwnerId -> strEncode (GRUpdateOwner_, gId, grOwnerId)
strP =
strP >>= \case
GRCreate_ -> GRCreate <$> (A.space *> strP)
GRUpdateStatus_ -> GRUpdateStatus <$> (A.space *> A.decimal) <*> (A.space *> strP)
GRUpdateOwner_ -> GRUpdateOwner <$> (A.space *> A.decimal) <*> (A.space *> A.decimal)
strP_ >>= \case
GRCreate_ -> GRCreate <$> strP
GRDelete_ -> GRDelete <$> strP
GRUpdateStatus_ -> GRUpdateStatus <$> A.decimal <*> _strP
GRUpdateOwner_ -> GRUpdateOwner <$> A.decimal <* A.space <*> A.decimal
instance StrEncoding GroupRegData where
strEncode GroupRegData {dbGroupId_, userGroupRegId_, dbContactId_, dbOwnerMemberId_, groupRegStatus_} =
@@ -314,6 +339,9 @@ readDirectoryData f =
putStrLn $
"Warning: duplicate group with ID " <> show gId <> ", group replaced."
pure $ M.insert gId gr m
GRDelete gId -> case M.lookup gId m of
Just _ -> pure $ M.delete gId m
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", deletion ignored.")
GRUpdateStatus gId groupRegStatus_ -> case M.lookup gId m of
Just gr -> pure $ M.insert gId gr {groupRegStatus_} m
Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", status update ignored.")
@@ -27,7 +27,7 @@ In the course of the audit, Trail of Bits assessed the maturity of the [simplexm
Explained below is our understanding of the issues, as well as fixes implemented by the SimpleX Chat team after project completion. The full security review is available via [Trail of Bits publications](https://github.com/trailofbits/publications/blob/master/reviews/SimpleXChat.pdf).
We are hugely thankful to Trail of Bits and their engineers for the work they did, helping us identify these issues and supporting the ongoing efforts to make Simple Chat more secure.
We are hugely thankful to Trail of Bits and their engineers for the work they did, helping us identify these issues and supporting the ongoing efforts to make SimpleX Chat more secure.
### Medium severity issues
@@ -0,0 +1,192 @@
---
layout: layouts/article.html
title: "SimpleX network: cryptographic design review by Trail of Bits, v6.1 released with better calls and user experience."
date: 2024-10-14
image: images/20221108-trail-of-bits.jpg
previewBody: blog_previews/20241014.html
permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"
---
# SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.
**Published:** Oct 14, 2024
[New security audit](#simplex-cryptographic-design-review-by-trail-of-bits):
- [review findings](#review-findings-our-comments-and-improvements), our comments and improvements.
- [next](#next-security-audit-in-2025): security audit in early 2025.
[What's new in v6.1](#whats-new-in-v61):
- [better calls](#better-calls).
- [better iOS notifications](#better-ios-notifications).
- [better user experience](#better-user-experience).
## SimpleX cryptographic design review by Trail of Bits
<img src="./images/20221108-trail-of-bits.jpg" width=240 class="float-to-right">
It's been almost two years since Trail of Bits did the first security assessment of SimpleX Chat.
Since then SimpleX Chat grew a lot, both in the number of users and in its functionality. We added XFTP &mdash; a protocol for sending files, &mdash; and XRCP &mdash; the protocol for using a mobile app profile from a desktop app. Messaging protocols also evolved a lot, adding private message routing and quantum resistant encryption.
Trail of Bits reviewed the design of protocols used in SimpleX network and applications in July 2024. Even though there are no critical issues, we made some security improvements based on this report.
[Trail of Bits](https://www.trailofbits.com/about) is a US based security and technology consultancy whose clients include big tech companies, governmental agencies and major blockchain projects. Its engineers reviewed the cryptographic design of the protocols used in SimpleX network and applications over a week:
- SimpleX Messaging Protocol (SMP), including a formal verification of currently used message queue negotiation protocol,
- the SMP agent protocol,
- the push notification system,
- the file transfer protocol (XFTP),
- the remote control protocol (XRCP),
- and the chat protocol.
There are 3 medium and 1 low severity findings, all of which require a high difficulty attack to exploit &mdash; the attacker would need to have a privileged access to the system, may need to know complex technical details, or must discover other weaknesses to exploit them. Additionally, there are 3 informational findings.
3 of these issues are improved in v6.1, and the remaining issues are accepted. Below we are commenting on these findings in detail, and also on the released improvements.
The full cryptographic design review is available [here](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/SimpleX_Design_Review_2024_Summary_Report_12_08_2024.pdf).
We are very thankful to Trail of Bits and their engineers for their work identifying these issues and helping us make SimpleX Chat more secure.
### Review findings, our comments and improvements
#### Protocols specifications (informational)
The review finding #1 is that the protocols specification is informal. We addressed [reported](https://github.com/simplex-chat/simplexmq/commit/7b6c86c6c1093cdae5ad2ee566655828076bc25c) [inconsistencies](https://github.com/simplex-chat/simplex-chat/commit/1cb3c25478db0f2a42c943f7469f5f9f75752a27), and we accept that we need to improve specification beyond verbose descriptions and ABNF syntax specification, and add algebraic notations and sequence diagrams. Having said that, the current specification correctly describes the implemented protocol, without any contradictions.
#### User-correlating attacks via introduced latency or via GET command of messaging protocol (medium and low severity)
These two findings #7 and #2 of the report relate to the attacks confirming that two known users communicate via observing their internet traffic.
The first attack is possible for a party that can introduce the latency in the network traffic. This attacker has to control some network node that passes the traffic of the sender &mdash; for example, it could be the sender's ISP, VPN provider, Tor entry node operator, the operator of the forwarding SMP server or a server hosting provider, etc. Such attacker can correlate delays in sender's traffic and the suspected recipient's traffic to confirm that they communicate.
The second attack relates to GET command used by iOS clients receiving notifications &mdash; depending on whether the server has the message, there will be a different number of packets sent, allowing the observer to determine if there was the message. While this comment is correct, in practice iOS clients only send GET commands when they receive notification, which also happens only when there is a message on the server, so in absolute majority of cases the number of packets will be the same.
These are not new findings &mdash; this type of attacks is covered in [threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#a-passive-adversary-able-to-monitor-a-set-of-senders-and-recipients): _a passive adversary able to monitor a set of senders and recipients **can** perform traffic correlation attacks against senders and recipients and correlate senders and recipients within the monitored set, frustrated by the number of users on the servers_.
As threat model states, this attack is more likely to be successful with the less busy servers, and also for the users with few connections.
The recommendation of the review is to add optional randomized latency to message delivery that would reduce the opportunities for traffic correlation attacks &mdash; we consider adding it in the future.
#### A compromised transport protocol allows more efficient correlation attacks (medium severity)
The finding #3 is about the incorrect statement in threat model for SMP and XFTP protocols: _a passive adversary, able to monitor a set of senders and recipients, **cannot**, even in case of a compromised transport protocol perform traffic correlation attacks with any increase in efficiency over a non-compromised transport protocol_.
For protocols prior to v6.1 it is only partially correct, as responses to the commands that create a messaging queue or a file chunk include the identifiers both for senders and for the recipients, so if any observers were to compromise transport protocol (TLS) and record these identifiers, then they were able to correlate message senders with the recipients (and file recipients with the file senders).
The solution to make this correlation impossible even in case of compromised TLS is to encrypt these identifiers, as proposed in the review, or, better, encrypt the whole transmission inside TLS.
However unlikely is TLS being compromised, we added additional [transport encryption layer in SMP protocol](https://github.com/simplex-chat/simplexmq/pull/1317/files), where it can be more important, and we are going to add the same layer of encryption in XFTP protocol later, where we [amended the threat model](https://github.com/simplex-chat/simplexmq/commit/7b6c86c6c1093cdae5ad2ee566655828076bc25c).
#### XRCP protocol recommendations (informational)
XRCP protocol is used for connecting desktop and mobile. There are two findings in the review:
- SHA256 was used as a KDF in XRCP (#4).
- there was no forward secrecy or break-in recovery between sessions (#5).
SHA256 is now [replaced with SHA3-256](https://github.com/simplex-chat/simplexmq/pull/1302/files), as was [recommended](https://www.ietf.org/archive/id/draft-josefsson-ntruprime-hybrid-01.html) by the internet draft about hybrid key agreement that XRCP uses.
Even though XRCP sessions are short lived, and usually the connection happens over local network, we added forward secrecy to XRCP sessions [here](https://github.com/simplex-chat/simplexmq/pull/1328/files) and [here](https://github.com/simplex-chat/simplex-chat/pull/4926/files) &mdash; each request from desktop app to mobile app is now encrypted with a new key derived from chain ratchets. This improves security of this connection.
We believe that it is unnecessary to have in-session break-in recovery in XRCP protocol, as there is break-in recovery between the sessions.
#### Device compromise can be hidden in some scenarios (medium)
The finding #6 in the report is about an attacker who was not only able to break into the device and get a copy of the database, which would be mitigated by break-in recovery in [double ratchet protocol](../docs/GLOSSARY.md#double-ratchet-algorithm), but also was able to modify the state of the app database and to substitute the addresses and cryptographic keys of the messaging queues used with some contact with other message queues that the attacker controls.
Even though this is a very hard attack, if successful, it would allow the attacker intercepting all messages with this contact.
Effectively, it is a [man-in-the-middle attack](../docs/GLOSSARY.md#man-in-the-middle-attack), where an intermediary is inserted via the app database modification. Such attack can be mitigated by periodic verification of security codes. Although, the attacker who was able to modify the state of the device, could have also modified the app itself, making it show the same security code as the compromised contact has, thus avoiding detection.
We accept that such an attack is possible, and we don't believe there is any viable defense against the attacker who can modify the device state. We may consider adding the measures to validate the database integrity, but they may be ineffective in case the app and/or operating system are compromised.
### Next: security audit in 2025
We are planning the implementation security assessment with Trail of Bits in the beginning of 2025. It will be a twice bigger assessment than we did in 2022 &mdash; it will cover both the core of the app and the handling of cryptographic secrets in the mobile applications.
## What's new in v6.1
This release has many user experience and stability improvements.
### Better calls
<img src="./images/20241014-calls.png" width="288" class="float-to-right">
This release improves reliability and usability of the calls. Now you can enable the camera and share the screen from the desktop app even if the call started as a voice call. We've also fixed several issues that prevented calls from connecting.
This is a substantial change, and some issues may have been introduced - please report them.
We will be further improving the calls interface in the app in the next versions.
### Better iOS notifications
iOS notifications were added [more than 2 years ago](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md), based on this [system design](./20220404-simplex-chat-instant-notifications.md). Until recently we made almost no improvements to them. As the number of iOS users is growing, their reliability is insufficient. In addition to that, once we started the work on improving them, we have found several important issues, one of which was introduced recently, when we improved the speed of creating new connections.
This release fixes many important issues with iOS notifications delivery in iOS app, improves app performance and reduces traffic required to manage notifications.
We also fixed several notification server issues, made change that almost completely prevents losing notifications when notification servers are restarted, and added real-time monitoring to diagnose any issues with iOS notifications delivery.
This work is not over iOS notifications in a decentralized network are complex and require more work. We will be further improving both client apps and servers to make their delivery stable.
### Better user experience
#### New conversation layout and customizable messages
<img src="./images/20241014-messages.png" width="288" class="float-to-right">
Messages are now grouped when they are sent sequentially, with less than 60 seconds between them. We also made message shapes configurable, and separated the messages in different days. When you scroll conversation quickly, there will be a floating date indication, allowing to find messages faster.
#### Improved switching between user profiles
<img src="./images/20241014-profiles2.png" width="288" class="float-to-right"> <img src="./images/20241014-profiles1.png" width="288" class="float-to-right">
Another improvement relates to switching between chat profiles. Previously, when you added multiple chat profiles to the app, there were two problems:
- you had to tap twice to get to some important functions in the app,
- anybody who could see your screen, could also see all your chat profiles.
We changed this design by making important functions available after tapping profile image once, and by only showing the previously used profile image to switch to it quickly, while switching to other profiles now requires scrolling to them or opening *Your chat profiles* screen.
You also can switch chat profile when creating a one-time invitation link.
#### Faster deletion, moderation and forwarding of messages
<img src="./images/20241014-forward.png" width="288" class="float-to-right">
You now can forward multiple messages at once - up to 20. If you are forwarding messages with files or media, and they were not received, the app will offer you to download them, and it will also allow forwarding messages without files. These messages will be "packed" into the smallest number of sent messages as possible. If there are no images and messages are not too large, it will be just one sent message containing all forwarded messages.
The previous version allowed deleting and moderating multiple messages. As most users now upgraded the app, we increased the maximum number of messages that can be deleted or moderated to 200 messages - in most cases all these deletions will be packed into one sent message.
## SimpleX network
Some links to answer the most common questions:
[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers).
[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users).
[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations).
[Frequently asked questions](../docs/FAQ.md).
Please also see our [website](https://simplex.chat).
## Please support us with your donations
Huge *thank you* to everybody who donated to SimpleX Chat!
Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations.
Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users.
Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
Your donations help us raise more funds &mdash; any amount, even the price of the cup of coffee, makes a big difference for us.
See [this section](https://github.com/simplex-chat/simplex-chat/tree/master#help-us-with-donations) for the ways to donate.
Thank you,
Evgeny
SimpleX Chat founder
+39
View File
@@ -0,0 +1,39 @@
---
layout: layouts/article.html
title: "Wireds Attack on Privacy"
date: 2024-10-16
previewBody: blog_previews/20241016.html
image: images/20241016-wired-privacy.jpg
imageWide: true
permalink: "/blog/20241016-wired-attack-on-privacy.html"
---
# Wireds Attack on Privacy
<img src="./images/20241016-wired-privacy.jpg" width="330" class="float-to-right">
The [Wired article](https://www.wired.com/story/neo-nazis-flee-telegram-encrypted-app-simplex/) by David Gilbert focusing on neo-Nazis moving to SimpleX Chat following the Telegram's changes in privacy policy is biased and misleading. By cherry-picking information from [the report](https://www.isdglobal.org/digital_dispatches/neo-nazi-accelerationists-seek-new-digital-refuge-amid-looming-telegram-crackdown/) by the Institute for Strategic Dialogue (ISD), Wired fails to mention that SimpleX network design prioritizes privacy in order to protect human rights defenders, journalists, and everyday users who value their privacy &mdash; many people feel safer using SimpleX than non-private apps, being protected from strangers contacting them.
Yes, privacy-focused SimpleX network offers encryption and anonymity &mdash; thats the point. To paint this as problematic solely because of who may use such apps misses the broader, critical context.
SimpleXs true strength lies in protection of [users' metadata](./20240416-dangers-of-metadata-in-messengers.md), which can reveal sensitive information about who is communicating, when, and how often. SimpleX protocols are designed to minimize metadata collection. For countless people, especially vulnerable groups, these features can be life-saving. Wired article ignores these essential protections, and overlooks the positive aspects of having such a unique design, as noted in the publication which [they link to](https://www.maargentino.com/is-telegrams-privacy-shift-driving-extremists-toward-simplex/):
> *“SimpleX also has a significant advantage when it comes to protecting metadata &mdash; the information that can reveal who youre talking to, when, and how often. SimpleX is designed with privacy at its core, minimizing the amount of metadata collected and ensuring that any temporary data necessary for functionality is not retained or linked to identifiable users.”*
Both publications referenced by Wired also explore how SimpleX design actually hinders extremist groups from spreading propaganda or building large networks. SimpleX design restricts message visibility and file retention, making it far from ideal for those looking to coordinate large networks. Yet these important qualities are ignored by Wired in favor of fear-mongering about encryption &mdash; an argument we've seen before when apps like Signal [faced similar treatment](https://foreignpolicy.com/2021/03/13/telegram-signal-apps-right-wing-extremism-islamic-state-terrorism-violence-europol-encrypted/). Ironically, Wired just a month earlier encouraged its readers to [adopt encrypted messaging apps](https://www.wired.com/story/gadget-lab-podcast-657/), making its current stance even more contradictory.
The vilification of apps that offer critically important privacy, anonymity, and encryption must stop. That a small share of users may abuse these tools doesnt justify broad criticism. Additionally, the lobbying for client-side scanning, which Wireds article seems to indirectly endorse, is not only dangerous but goes against fundamental principles of free speech and personal security. We strongly oppose the use of private communications for any kind of monitoring, including AI training, which would undermine the very trust encryption is designed to build.
Its alarming to see Wired not only criticize SimpleX for its strong privacy protections but also subtly blame the European Court of Human Rights for [upholding basic human rights](https://www.theregister.com/2024/02/15/echr_backdoor_encryption/) by rejecting laws that would force encrypted apps to scan and hand over private messages before encryption. Wired writes:
> *…European Court of Human Rights decision in February of this year ruled that forcing encrypted messaging apps to provide a backdoor to law enforcement was illegal. This decision undermined the EUs controversial proposal that would potentially force encrypted messaging apps to scan all user content for identifiers of child sexual abuse material.*
This commentary is both inappropriate and misguided &mdash; it plays into the hands of anti-privacy lobbyists attempting to criminalize access to private communications. Framing privacy and anonymity as tools for criminals ignores the reality that these protections are essential for millions of legitimate users, from activists to journalists, to ordinary citizens. Client-side scanning can't have any meaningful effect on reducing CSAM distribution, instead resulting in increase of crime and abuse when criminals get access to this data.
We need to correct this narrative. The real danger lies not in protecting communication, but in failing to do so. Privacy apps like SimpleX are crucial, not just for those resisting mass surveillance, but for everyone who values the right to communicate without fear of their conversations being monitored or misused. This is a right we must defend and incorporate into law, [as we wrote before](./20240704-future-of-privacy-enforcing-privacy-standards.md).
Wired could have stood on the right side of this battle and helped normalize the demand for privacy, genuinely protecting people from criminals and from the exploitation of the increasingly AI-enabled mass surveillance. Instead they chose the path of spreading fear and uncertainty of encrypted messaging and tools that enable privacy and anonymity.
Spreading misinformation about privacy and security undermines trust in the tools that protect us, making it easier to justify more invasive surveillance measures that chip away at our civil liberties.
Wired did not respond to our request for comment.
+12 -1
View File
@@ -1,8 +1,19 @@
# Blog
Oct 14, 2024 [SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md)
New security audit: Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps.
What's new in v6.1:
- Better calls: switch audio and video during the call.
- Better iOS notifications: improved delivery, reduced traffic usage.
- Better user experience: switch chat profiles, customizable message shapes, forward up to 20 messages.
---
Aug 14, 2024 [SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md)
[SimpleX Chat: vision and funding 2.0](#simplex-chat-vision-and-funding-20): past, present, future.
SimpleX Chat: vision and funding 2.0: past, present, future.
Announcing the investment from Jack Dorsey and Asymmetric.
Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+1 -1
View File
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: b8971a31bcb82fffabcb792c9afd6bc4a96ec649
tag: 0b4492879fd36f0b517dca8f5e36dba187bba686
source-repository-package
type: git
+4 -2
View File
@@ -8,9 +8,11 @@ revision: 23.04.2024
While great care is taken to ensure the highest level of security and privacy in SimpleX network servers and clients, all software can have flaws, and we believe it is a critical part of an organization's social responsibility to minimize the impact of these flaws through continual vulnerability discovery efforts, defense in depth design, and prompt remediation and notification.
The security assessment of SimpleX cryptography and networking was done by Trail of Bits in [November 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
The implementation security assessment of SimpleX cryptography and networking was done by Trail of Bits in [November 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
We are planning design review of SimpleX protocols in July 2024 and implementation review in December 2024/January 2025.
The cryptographic review of SimpleX protocols design was done by Trail of Bits in [July 2024](../blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md).
We are planning implementation security assessment in early 2025.
## Reporting security issues
+414 -62
View File
@@ -1,13 +1,16 @@
---
title: Hosting your own SMP Server
revision: 03.06.2024
revision: 12.10.2024
---
| Updated 28.05.2024 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) |
# Hosting your own SMP Server
| Updated 12.10.2024 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) |
### Table of Contents
- [Hosting your own SMP server](#hosting-your-own-smp-server)
- [Quick start](#quick-start)
- [Detailed guide](#detailed-guide)
- [Overview](#overview)
- [Installation](#installation)
- [Configuration](#configuration)
@@ -29,17 +32,218 @@ revision: 03.06.2024
- [Updating your SMP server](#updating-your-smp-server)
- [Configuring the app to use the server](#configuring-the-app-to-use-the-server)
# Hosting your own SMP Server
## Quick start
## Overview
To create SMP server, you'll need:
- VPS or any other server.
- Your server domain, with A and AAAA records specifying server IPv4 and IPv6 addresses (`smp1.example.com`)
- A basic Linux knowledge.
*Please note*: while you can run an SMP server without a domain name, in the near future client applications will start using server domain name in the invitation links (instead of `simplex.chat` domain they use now). In case a server does not have domain name and server pages (see below), the clients will be generaing the links with `simplex:` scheme that cannot be opened in the browsers.
1. Install server with [Installation script](https://github.com/simplex-chat/simplexmq#using-installation-script).
2. Adjust firewall:
```sh
ufw allow 80/tcp &&\
ufw allow 443/tcp &&\
ufw allow 5223/tcp
```
3. Init server:
Replace `smp1.example.com` with your actual server domain.
```sh
su smp -c 'smp-server init --yes \
--store-log \
--no-password \
--control-port \
--socks-proxy \
--source-code \
--fqdn=smp1.example.com
```
4. Install tor:
```sh
CODENAME="$(lsb_release -c | awk '{print $2}')"
echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main
deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main" > /etc/apt/sources.list.d/tor.list &&\
curl --proto '=https' --tlsv1.2 -sSf https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null &&\
apt update && apt install -y tor deb.torproject.org-keyring
```
5. Configure tor:
```sh
tor-instance-create tor2 &&\
mkdir /var/lib/tor/simplex-smp/ &&\
chown debian-tor:debian-tor /var/lib/tor/simplex-smp/ &&\
chmod 700 /var/lib/tor/simplex-smp/
```
```sh
vim /etc/tor/torrc
```
Paste the following:
```sh
# Enable log (otherwise, tor doesn't seem to deploy onion address)
Log notice file /var/log/tor/notices.log
# Enable single hop routing (2 options below are dependencies of the third) - It will reduce the latency at the cost of lower anonimity of the server - as SMP-server onion address is used in the clients together with public address, this is ok. If you deploy SMP-server with onion-only address, keep standard configuration.
SOCKSPort 0
HiddenServiceNonAnonymousMode 1
HiddenServiceSingleHopMode 1
# smp-server hidden service host directory and port mappings
HiddenServiceDir /var/lib/tor/simplex-smp/
HiddenServicePort 5223 localhost:5223
HiddenServicePort 443 localhost:443
```
```sh
vim /etc/tor/instances/tor2/torrc
```
Paste the following:
```sh
# Log tor to systemd daemon
Log notice syslog
# Listen to local 9050 port for socks proxy
SocksPort 9050
```
6. Start tor:
```sh
systemctl enable tor &&\
systemctl start tor &&\
systemctl restart tor &&\
systemctl enable --now tor@tor2
```
7. Install Caddy:
```sh
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl &&\
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg &&\
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list &&\
sudo apt update && sudo apt install caddy
```
8. Configure Caddy:
```sh
vim /etc/caddy/Caddyfile
```
Replace `smp1.example.com` with your actual server domain. Paste the following:
```
http://smp1.example.com {
redir https://smp1.example.com{uri} permanent
}
smp1.example.com:8443 {
tls {
key_type rsa4096
}
}
```
```sh
vim /usr/local/bin/simplex-servers-certs
```
Replace `smp1.example.com` with your actual server domain. Paste the following:
```sh
#!/usr/bin/env sh
set -eu
user='smp'
group="$user"
domain='smp1.example.com'
folder_in="/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${domain}"
folder_out='/etc/opt/simplex'
key_name='web.key'
cert_name='web.crt'
# Copy certifiacte from Caddy directory to smp-server directory
cp "${folder_in}/${domain}.crt" "${folder_out}/${cert_name}"
# Assign correct permissions
chown "$user":"$group" "${folder_out}/${cert_name}"
# Copy certifiacte key from Caddy directory to smp-server directory
cp "${folder_in}/${domain}.key" "${folder_out}/${key_name}"
# Assign correct permissions
chown "$user":"$group" "${folder_out}/${key_name}"
```
```sh
chmod +x /usr/local/bin/simplex-servers-certs
```
```sh
sudo crontab -e
```
Paste the following:
```sh
# Every week on 00:20 sunday
20 0 * * 0 /usr/local/bin/simplex-servers-certs
```
9. Enable and start Caddy service:
Wait until "good to go" has been printed.
```sh
systemctl enable --now caddy &&\
sleep 10 &&\
/usr/local/bin/simplex-servers-certs &&\
echo 'good to go'
```
10. Enable and start smp-server:
```sh
systemctl enable --now smp-server.service
```
11. Print your address:
```sh
smp="$(journalctl --output cat -q _SYSTEMD_INVOCATION_ID="$(systemctl show -p InvocationID --value smp-server)" | grep -m1 'Server address:' | awk '{print $NF}' | sed 's/:443.*//')"
tor="$(cat /var/lib/tor/simplex-smp/hostname)"
echo "$smp,$tor"
```
## Detailed guide
### Overview
SMP server is the relay server used to pass messages in SimpleX network. SimpleX Chat apps have preset servers (for mobile apps these are smp11, smp12 and smp14.simplex.im), but you can easily change app configuration to use other servers.
SimpleX clients only determine which server is used to receive the messages, separately for each contact (or group connection with a group member), and these servers are only temporary, as the delivery address can change.
To create SMP server, you'll need:
1. VPS or any other server.
2. Your own domain, pointed at the server (`smp.example.com`)
3. A basic Linux knowledge.
_Please note_: when you change the servers in the app configuration, it only affects which servers will be used for the new contacts, the existing contacts will not automatically move to the new servers, but you can move them manually using ["Change receiving address"](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md#change-your-delivery-address-beta) button in contact/member information pages it will be automated in the future.
## Installation
### Installation
1. First, install `smp-server`:
@@ -82,8 +286,10 @@ Manual installation requires some preliminary actions:
```sh
# For Ubuntu
sudo ufw allow 5223/tcp
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
# For Fedora
sudo firewall-cmd --permanent --add-port=5223/tcp && \
sudo firewall-cmd --permanent --add-port=5223/tcp --add-port=443/tcp --add-port=80/tcp && \
sudo firewall-cmd --reload
```
@@ -102,6 +308,7 @@ Manual installation requires some preliminary actions:
LimitNOFILE=65535
KillSignal=SIGINT
TimeoutStopSec=infinity
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
@@ -109,7 +316,7 @@ Manual installation requires some preliminary actions:
And execute `sudo systemctl daemon-reload`.
## Configuration
### Configuration
To see which options are available, execute `smp-server` without flags:
@@ -129,7 +336,7 @@ You can get further help by executing `sudo su smp -c "smp-server <command> -h"`
After that, we need to configure `smp-server`:
### Interactively
#### Interactively
Execute the following command:
@@ -159,7 +366,7 @@ These statistics include daily counts of created, secured and deleted queues, se
Enter your domain or ip address that your smp-server is running on - it will be included in server certificates and also printed as part of server address.
### Via command line options
#### Via command line options
Execute the following command:
@@ -223,7 +430,7 @@ Server address: smp://d5fcsc7hhtPpexYUbI2XPxDbyU2d3WsVmROimcL90ss=:V8ONoJ6ICwnrZ
The server address above should be used in your client configuration, and if you added server password it should only be shared with the other people who you want to allow using your server to receive the messages (all your contacts will be able to send messages - it does not require a password). If you passed IP address or hostnames during the initialisation, they will be printed as part of server address, otherwise replace `<hostnames>` with the actual server hostnames.
## Further configuration
### Further configuration
All generated configuration, along with a description for each parameter, is available inside configuration file in `/etc/opt/simplex/smp-server.ini` for further customization. Depending on the smp-server version, the configuration file looks something like this:
@@ -245,26 +452,26 @@ source_code: https://github.com/simplex-chat/simplexmq
# condition_amendments: link
# Server location and operator.
server_country: <YOUR_SERVER_LOCATION>
operator: <YOUR_NAME>
operator_country: <YOUR_LOCATION>
website: <WEBSITE_IF_AVAILABLE>
# server_country: ISO-3166 2-letter code
# operator: entity (organization or person name)
# operator_country: ISO-3166 2-letter code
# website:
# Administrative contacts.
#admin_simplex: SimpleX address
admin_email: <EMAIL>
# admin_simplex: SimpleX address
# admin_email:
# admin_pgp:
# admin_pgp_fingerprint:
# Contacts for complaints and feedback.
# complaints_simplex: SimpleX address
complaints_email: <COMPLAINTS_EMAIL>
# complaints_email:
# complaints_pgp:
# complaints_pgp_fingerprint:
# Hosting provider.
hosting: <HOSTING_PROVIDER_NAME>
hosting_country: <HOSTING_PROVIDER_LOCATION>
# hosting: entity (organization or person name)
# hosting_country: ISO-3166 2-letter code
[STORE_LOG]
# The server uses STM memory for persistence,
@@ -278,6 +485,7 @@ enable: on
# they are preserved in the .bak file until the next restart.
restore_messages: on
expire_messages_days: 21
expire_ntfs_hours: 24
# Log daily server statistics to CSV file
log_stats: on
@@ -294,11 +502,17 @@ new_queues: on
# with the users who you want to allow creating messaging queues on your server.
# create_password: password to create new queues (any printable ASCII characters without whitespace, '@', ':' and '/')
# control_port_admin_password:
# control_port_user_password:
[TRANSPORT]
# host is only used to print server address on start
host: <your server domain/ip>
port: 5223
# Host is only used to print server address on start.
# You can specify multiple server ports.
host: <domain/ip>
port: 5223,443
log_tls_errors: off
# Use `websockets: 443` to run websockets server in addition to plain TLS.
websockets: off
# control_port: 5224
@@ -310,7 +524,7 @@ websockets: off
# required_host_mode: off
# The domain suffixes of the relays you operate (space-separated) to count as separate proxy statistics.
# own_server_domains: <your domain suffixes>
# own_server_domains:
# SOCKS proxy port for forwarding messages to destination servers.
# You may need a separate instance of SOCKS proxy for incoming single-hop requests.
@@ -326,7 +540,7 @@ websockets: off
[INACTIVE_CLIENTS]
# TTL and interval to check inactive clients
disconnect: off
# ttl: 43200
# ttl: 21600
# check_interval: 3600
[WEB]
@@ -336,18 +550,18 @@ static_path: /var/opt/simplex/www
# Run an embedded server on this port
# Onion sites can use any port and register it in the hidden service config.
# Running on a port 80 may require setting process capabilities.
# http: 8000
#http: 8000
# You can run an embedded TLS web server too if you provide port and cert and key files.
# Not required for running relay on onion address.
# https: 443
# cert: /etc/opt/simplex/web.cert
# key: /etc/opt/simplex/web.key
https: 443
cert: /etc/opt/simplex/web.crt
key: /etc/opt/simplex/web.key
```
## Server security
### Server security
### Initialization
#### Initialization
Although it's convenient to initialize smp-server configuration directly on the server, operators **ARE ADVISED** to initialize smp-server fully offline to protect your SMP server CA private key.
@@ -367,7 +581,7 @@ Follow the steps to quickly initialize the server offline:
rsync -hzasP $HOME/simplex/smp/config/ <server_user>@<server_address>:/etc/opt/simplex/
```
### Private keys
#### Private keys
Connection to the smp server occurs via a TLS connection. During the TLS handshake, the client verifies smp-server CA and server certificates by comparing its fingerprint with the one included in server address. If server TLS credential is compromised, this key can be used to sign a new one, keeping the same server identity and established connections. In order to protect your smp-server from bad actors, operators **ARE ADVISED** to move CA private key to a safe place. That could be:
@@ -392,7 +606,7 @@ Follow the steps to secure your CA keys:
rm /etc/opt/simplex/ca.key
```
### Online certificate rotation
#### Online certificate rotation
Operators of smp servers **ARE ADVISED** to rotate online certificate regularly (e.g., every 3 months). In order to do this, follow the steps:
@@ -468,9 +682,9 @@ Operators of smp servers **ARE ADVISED** to rotate online certificate regularly
10. Done!
## Tor: installation and configuration
### Tor: installation and configuration
### Installation for onion address
#### Installation for onion address
SMP-server can also be deployed to be available via [Tor](https://www.torproject.org) network. Run the following commands as `root` user.
@@ -526,6 +740,7 @@ SMP-server can also be deployed to be available via [Tor](https://www.torproject
# smp-server hidden service host directory and port mappings
HiddenServiceDir /var/lib/tor/simplex-smp/
HiddenServicePort 5223 localhost:5223
HiddenServicePort 443 localhost:443
```
- Create directories:
@@ -550,7 +765,7 @@ SMP-server can also be deployed to be available via [Tor](https://www.torproject
cat /var/lib/tor/simplex-smp/hostname
```
### SOCKS port for SMP PROXY
#### SOCKS port for SMP PROXY
SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp servers available exclusively through [Tor](https://www.torproject.org) network to be accessible to the clients that do not use Tor. Run the following commands as `root` user.
@@ -597,9 +812,11 @@ SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp
...
```
## Server information page
### Server information page
SMP-server versions starting from `v5.8.0` can be configured to serve Web page with server information that can include admin info, server info, provider info, etc. Run the following commands as `root` user.
SMP server **SHOULD** be configured to serve Web page with server information that can include admin info, server info, provider info, etc. It will also serve connection links, generated using the mobile/desktop apps. Run the following commands as `root` user.
_Please note:_ this configuration is supported since `v6.1.0-beta.2`.
1. Add the following to your smp-server configuration (please modify fields in [INFORMATION] section to include relevant information):
@@ -608,8 +825,19 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
```
```ini
[TRANSPORT]
# host is only used to print server address on start
host: <domain/ip>
port: 443,5223
websockets: off
log_tls_errors: off
control_port: 5224
[WEB]
https: 443
static_path: /var/opt/simplex/www
cert: /etc/opt/simplex/web.crt
key: /etc/opt/simplex/web.key
[INFORMATION]
# AGPLv3 license requires that you make any source code modifications
@@ -678,16 +906,23 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
[Full Caddy instllation instructions](https://caddyserver.com/docs/install)
3. Replace Caddy configuration with the following (don't forget to replace `<YOUR_DOMAIN>`):
3. Replace Caddy configuration with the following:
Please replace `YOUR_DOMAIN` with your actual domain (smp.example.com).
```sh
vim /etc/caddy/Caddyfile
```
```caddy
<YOUR_DOMAIN> {
root * /var/opt/simplex/www
file_server
```
http://YOUR_DOMAIN {
redir https://YOUR_DOMAIN{uri} permanent
}
YOUR_DOMAIN:8443 {
tls {
key_type rsa4096
}
}
```
@@ -697,17 +932,75 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
systemctl enable --now caddy
```
5. Upgrade your smp-server to latest version - [Updating your smp server](#updating-your-smp-server)
5. Create script to copy certificates to your smp directory:
6. Access the webpage you've deployed from your browser. You should see the smp-server information that you've provided in your ini file.
Please replace `YOUR_DOMAIN` with your actual domain (smp.example.com).
## Documentation
```sh
vim /usr/local/bin/simplex-servers-certs
```
```sh
#!/usr/bin/env sh
set -eu
user='smp'
group="$user"
domain='HOST'
folder_in="/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${domain}"
folder_out='/etc/opt/simplex'
key_name='web.key'
cert_name='web.crt'
# Copy certifiacte from Caddy directory to smp-server directory
cp "${folder_in}/${domain}.crt" "${folder_out}/${cert_name}"
# Assign correct permissions
chown "$user":"$group" "${folder_out}/${cert_name}"
# Copy certifiacte key from Caddy directory to smp-server directory
cp "${folder_in}/${domain}.key" "${folder_out}/${key_name}"
# Assign correct permissions
chown "$user":"$group" "${folder_out}/${key_name}"
```
6. Make the script executable and execute it:
```sh
chmod +x /usr/local/bin/simplex-servers-certs && /usr/local/bin/simplex-servers-certs
```
7. Check if certificates were copied:
```sh
ls -haltr /etc/opt/simplex/web*
```
8. Create cronjob to copy certificates to smp directory in timely manner:
```sh
sudo crontab -e
```
```sh
# Every week on 00:20 sunday
20 0 * * 0 /usr/local/bin/simplex-servers-certs
```
9. Then:
- If you're running at least `v6.1.0-beta.2`, [restart the server](#systemd-commands).
- If you're running below `v6.1.0-beta.2`, [upgrade the server](#updating-your-smp-server).
10. Access the webpage you've deployed from your browser (`https://smp.example.org`). You should see the smp-server information that you've provided in your ini file.
### Documentation
All necessary files for `smp-server` are located in `/etc/opt/simplex/` folder.
Stored messages, connections, statistics and server log are located in `/var/opt/simplex/` folder.
### SMP server address
#### SMP server address
SMP server address has the following format:
@@ -727,7 +1020,7 @@ smp://<fingerprint>[:<password>]@<public_hostname>[,<onion_hostname>]
Your configured hostname(s) of `smp-server`. You can check your configured hosts in `/etc/opt/simplex/smp-server.ini`, under `[TRANSPORT]` section in `host:` field.
### Systemd commands
#### Systemd commands
To start `smp-server` on host boot, run:
@@ -786,16 +1079,18 @@ Nov 23 19:23:21 5588ab759e80 smp-server[30878]: not expiring inactive clients
Nov 23 19:23:21 5588ab759e80 smp-server[30878]: creating new queues requires password
```
### Monitoring
#### Monitoring
You can enable `smp-server` statistics for `Grafana` dashboard by setting value `on` in `/etc/opt/simplex/smp-server.ini`, under `[STORE_LOG]` section in `log_stats:` field.
Logs will be stored as `csv` file in `/var/opt/simplex/smp-server-stats.daily.log`. Fields for the `csv` file are:
```sh
fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,monthMsgQueues,msgSentNtf,msgRecvNtf,dayCountNtf,weekCountNtf,monthCountNtf,qCount,msgCount,msgExpired,qDeletedNew,qDeletedSecured,pRelays_pRequests,pRelays_pSuccesses,pRelays_pErrorsConnect,pRelays_pErrorsCompat,pRelays_pErrorsOther,pRelaysOwn_pRequests,pRelaysOwn_pSuccesses,pRelaysOwn_pErrorsConnect,pRelaysOwn_pErrorsCompat,pRelaysOwn_pErrorsOther,pMsgFwds_pRequests,pMsgFwds_pSuccesses,pMsgFwds_pErrorsConnect,pMsgFwds_pErrorsCompat,pMsgFwds_pErrorsOther,pMsgFwdsOwn_pRequests,pMsgFwdsOwn_pSuccesses,pMsgFwdsOwn_pErrorsConnect,pMsgFwdsOwn_pErrorsCompat,pMsgFwdsOwn_pErrorsOther,pMsgFwdsRecv,qSub,qSubAuth,qSubDuplicate,qSubProhibited,msgSentAuth,msgSentQuota,msgSentLarge
fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,monthMsgQueues,msgSentNtf,msgRecvNtf,dayCountNtf,weekCountNtf,monthCountNtf,qCount,msgCount,msgExpired,qDeletedNew,qDeletedSecured,pRelays_pRequests,pRelays_pSuccesses,pRelays_pErrorsConnect,pRelays_pErrorsCompat,pRelays_pErrorsOther,pRelaysOwn_pRequests,pRelaysOwn_pSuccesses,pRelaysOwn_pErrorsConnect,pRelaysOwn_pErrorsCompat,pRelaysOwn_pErrorsOther,pMsgFwds_pRequests,pMsgFwds_pSuccesses,pMsgFwds_pErrorsConnect,pMsgFwds_pErrorsCompat,pMsgFwds_pErrorsOther,pMsgFwdsOwn_pRequests,pMsgFwdsOwn_pSuccesses,pMsgFwdsOwn_pErrorsConnect,pMsgFwdsOwn_pErrorsCompat,pMsgFwdsOwn_pErrorsOther,pMsgFwdsRecv,qSub,qSubAuth,qSubDuplicate,qSubProhibited,msgSentAuth,msgSentQuota,msgSentLarge,msgNtfs,msgNtfNoSub,msgNtfLost,qSubNoMsg,msgRecvGet,msgGet,msgGetNoMsg,msgGetAuth,msgGetDuplicate,msgGetProhibited,psSubDaily,psSubWeekly,psSubMonthly,qCount2,ntfCreated,ntfDeleted,ntfSub,ntfSubAuth,ntfSubDuplicate,ntfCount,qDeletedAllB,qSubAllB,qSubEnd,qSubEndB,ntfDeletedB,ntfSubB,msgNtfsB,msgNtfExpired
```
#### Fields description
| Field number | Field name | Field Description |
| ------------- | ---------------------------- | -------------------------- |
| 1 | `fromTime` | Date of statistics |
@@ -856,6 +1151,34 @@ fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,m
| 45 | `msgSentAuth` | Authentication errors |
| 46 | `msgSentQuota` | Quota errors |
| 47 | `msgSentLarge` | Large message errors |
| 48 | `msgNtfs` | XXXXXXXXXXXXXXXXXXXX |
| 49 | `msgNtfNoSub` | XXXXXXXXXXXXXXXXXXXX |
| 50 | `msgNtfLost` | XXXXXXXXXXXXXXXXXXXX |
| 51 | `qSubNoMsg` | Removed, always 0 |
| 52 | `msgRecvGet` | XXXXXXXXXXXXXXXXX |
| 53 | `msgGet` | XXXXXXXXXXXXXXXXX |
| 54 | `msgGetNoMsg` | XXXXXXXXXXXXXXXXX |
| 55 | `msgGetAuth` | XXXXXXXXXXXXXXXXX |
| 56 | `msgGetDuplicate` | XXXXXXXXXXXXXXXXX |
| 57 | `msgGetProhibited` | XXXXXXXXXXXXXXXXX |
| 58 | `psSub_dayCount` | Removed, always 0 |
| 59 | `psSub_weekCount` | Removed, always 0 |
| 60 | `psSub_monthCount` | Removed, always 0 |
| 61 | `qCount` | XXXXXXXXXXXXXXXXX |
| 62 | `ntfCreated` | XXXXXXXXXXXXXXXXX |
| 63 | `ntfDeleted` | XXXXXXXXXXXXXXXXX |
| 64 | `ntfSub` | XXXXXXXXXXXXXXXXX |
| 65 | `ntfSubAuth` | XXXXXXXXXXXXXXXXX |
| 66 | `ntfSubDuplicate` | XXXXXXXXXXXXXXXXX |
| 67 | `ntfCount` | XXXXXXXXXXXXXXXXX |
| 68 | `qDeletedAllB` | XXXXXXXXXXXXXXXXX |
| 69 | `qSubAllB` | XXXXXXXXXXXXXXXXX |
| 70 | `qSubEnd` | XXXXXXXXXXXXXXXXX |
| 71 | `qSubEndB` | XXXXXXXXXXXXXXXXX |
| 72 | `ntfDeletedB` | XXXXXXXXXXXXXXXXX |
| 73 | `ntfSubB` | XXXXXXXXXXXXXXXXX |
| 74 | `msgNtfsB` | XXXXXXXXXXXXXXXXX |
| 75 | `msgNtfExpired` | XXXXXXXXXXXXXXXXX |
To import `csv` to `Grafana` one should:
@@ -863,83 +1186,112 @@ To import `csv` to `Grafana` one should:
2. Allow local mode by appending following:
```sh
[plugin.marcusolsson-csv-datasource]
allow_local_mode = true
```
```sh
[plugin.marcusolsson-csv-datasource]
allow_local_mode = true
```
... to `/etc/grafana/grafana.ini`
... to `/etc/grafana/grafana.ini`
3. Add a CSV data source:
- In the side menu, click the Configuration tab (cog icon)
- Click Add data source in the top-right corner of the Data Sources tab
- Enter "CSV" in the search box to find the CSV data source
- Click the search result that says "CSV"
- In URL, enter a file that points to CSV content
- In the side menu, click the Configuration tab (cog icon)
- Click Add data source in the top-right corner of the Data Sources tab
- Enter "CSV" in the search box to find the CSV data source
- Click the search result that says "CSV"
- In URL, enter a file that points to CSV content
4. You're done! You should be able to create your own dashboard with statistics.
For further documentation, see: [CSV Data Source for Grafana - Documentation](https://grafana.github.io/grafana-csv-datasource/)
## Updating your SMP server
### Updating your SMP server
To update your smp-server to latest version, choose your installation method and follow the steps:
- Manual deployment
1. Stop the server:
```sh
sudo systemctl stop smp-server
```
2. Update the binary:
```sh
curl -L https://github.com/simplex-chat/simplexmq/releases/latest/download/smp-server-ubuntu-20_04-x86-64 -o /usr/local/bin/smp-server && chmod +x /usr/local/bin/smp-server
```
3. Start the server:
```sh
sudo systemctl start smp-server
```
- [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script)
1. Execute the followin command:
```sh
sudo simplex-servers-update
```
To install specific version, run:
```sh
export VER=<version_from_github_releases> &&\
sudo -E simplex-servers-update
```
2. Done!
- [Docker container](https://github.com/simplex-chat/simplexmq#using-docker)
1. Stop and remove the container:
```sh
docker rm $(docker stop $(docker ps -a -q --filter ancestor=simplexchat/smp-server --format="\{\{.ID\}\}"))
```
2. Pull latest image:
```sh
docker pull simplexchat/smp-server:latest
```
3. Start new container:
```sh
docker run -d \
-p 5223:5223 \
-p 443:443 \
-v $HOME/simplex/smp/config:/etc/opt/simplex:z \
-v $HOME/simplex/smp/logs:/var/opt/simplex:z \
simplexchat/smp-server:latest
```
- [Linode Marketplace](https://www.linode.com/marketplace/apps/simplex-chat/simplex-chat/)
1. Pull latest images:
```sh
docker-compose --project-directory /etc/docker/compose/simplex pull
```
2. Restart the containers:
```sh
docker-compose --project-directory /etc/docker/compose/simplex up -d --remove-orphans
```
3. Remove obsolete images:
```sh
docker image prune
```
## Configuring the app to use the server
### Configuring the app to use the server
To configure the app to use your messaging server copy it's full address, including password, and add it to the app. You have an option to use your server together with preset servers or without them - you can remove or disable them.
+4 -1
View File
@@ -6,7 +6,7 @@ revision: 16.07.2024
# Transparency Reports
**Updated**: Jul 16, 2024
**Updated**: Oct 14, 2024
SimpleX Chat Ltd. is a company registered in the UK it develops communication software enabling users to operate and communicate via SimpleX network, without user profile identifiers of any kind, and without having their data hosted by any network infrastructure operators.
@@ -23,6 +23,9 @@ Our objective is to consistently ensure that no user data and absolute minimum o
- [Trust in servers](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#trust-in-servers)
- [Encryption Primitives Used](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#encryption-primitives-used)
- [Threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#threat-model)
- Security assessments:
- Trail of Bits, SimpleX cryptography and networking, [October 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
- Trail of Bits, the cryptographic review of SimpleX protocols design, [July 2024](../blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md).
Have a more specific question? Reach out to us via [SimpleX Chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) or via email [chat@simplex.chat](mailto:chat@simplex.chat).
+1 -1
View File
@@ -226,7 +226,7 @@ While introduced members establish connection inside group, inviting member forw
### Member roles
Currently members can have one of three roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages.
Currently members can have one of four roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages.
### Messages to manage groups and add members
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplex-chat
version: 6.1.0.7
version: 6.1.1.0
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme
+9 -4
View File
@@ -246,7 +246,7 @@ const callCrypto = callCryptoFunction()
declare var RTCRtpScriptTransform: {
prototype: RTCRtpScriptTransform
new (worker: Worker, options?: any): RTCRtpScriptTransform
new (worker: Worker, options?: any, transfer?: any[] | undefined): RTCRtpScriptTransform
}
enum TransformOperation {
@@ -316,6 +316,8 @@ const allowSendScreenAudio = false
// will be stored here and then set when the call will be ready to process them
let afterCallInitializedCandidates: RTCIceCandidateInit[] = []
const stopTrackOnAndroid = false
const processCommand = (function () {
type RTCRtpSenderWithEncryption = RTCRtpSender & {
createEncodedStreams: () => TransformStream
@@ -1141,7 +1143,8 @@ const processCommand = (function () {
// doing it vice versa gives an error like "too many cameras were open" on some Android devices or webViews
// which means the second camera will never be opened
for (const t of source == CallMediaSource.Mic ? call.localStream.getAudioTracks() : call.localStream.getVideoTracks()) {
t.stop()
if (isDesktop || source != CallMediaSource.Mic || stopTrackOnAndroid) t.stop()
else t.enabled = false
call.localStream.removeTrack(t)
}
let localStream: MediaStream
@@ -1200,7 +1203,7 @@ const processCommand = (function () {
if (!localStream || !oldCamera || !videos) return
if (!inactiveCallMediaSources.mic) {
localStream.getAudioTracks().forEach((elem) => elem.stop())
localStream.getAudioTracks().forEach((elem) => (isDesktop || stopTrackOnAndroid ? elem.stop() : (elem.enabled = false)))
localStream.getAudioTracks().forEach((elem) => localStream.removeTrack(elem))
}
if (!inactiveCallMediaSources.camera || oldCamera != newCamera) {
@@ -1474,7 +1477,9 @@ const processCommand = (function () {
if (enable) {
transceiver.sender.replaceTrack(t)
} else {
t.stop()
if (isDesktop || t.kind == CallMediaType.Video || stopTrackOnAndroid) t.stop()
else t.enabled = false
s.removeTrack(t)
transceiver.sender.replaceTrack(null)
}
+4
View File
@@ -11,4 +11,8 @@ package direct-sqlcipher
extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib
flags: +openssl
package rocksdb-haskell-jprupp
extra-include-dirs: /opt/homebrew/opt/rocksdb/include
extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib
test-show-details: direct
@@ -38,6 +38,30 @@
</description>
<releases>
<release version="6.1.0" date="2024-10-14">
<url type="details">https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html</url>
<description>
<p>New in v6.1:</p>
<p>Better security:</p>
<ul>
<li>SimpleX protocols reviewed by Trail of Bits.</li>
<li>security improvements (don't worry, there is nothing critical there).</li>
</ul>
<p>Better calls:</p>
<ul>
<li>you can switch audio and video during the call.</li>
<li>share the screen from desktop app.</li>
</ul>
<p>Better user experience:</p>
<ul>
<li>switch chat profile for 1-time invitations.</li>
<li>customizable message shape.</li>
<li>better message dates.</li>
<li>forward up to 20 messages at once.</li>
<li>delete or moderate up to 200 messages.</li>
</ul>
</description>
</release>
<release version="6.0.4" date="2024-08-31">
<url type="details">https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html</url>
<description>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."b8971a31bcb82fffabcb792c9afd6bc4a96ec649" = "1p6m390ngcsp7i7vy0m0zxh167gkbciavva9a00l6pxwzaz9qmpi";
"https://github.com/simplex-chat/simplexmq.git"."0b4492879fd36f0b517dca8f5e36dba187bba686" = "0rrkq84vx9n6rq67v0awfgnwpq3mmyx3rdzg6k9xwir1397bhf7r";
"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";
+2 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
version: 6.1.0.7
version: 6.1.1.0
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat
@@ -149,6 +149,7 @@ library
Simplex.Chat.Migrations.M20240827_calls_uuid
Simplex.Chat.Migrations.M20240920_user_order
Simplex.Chat.Migrations.M20241008_indexes
Simplex.Chat.Migrations.M20241010_contact_requests_contact_id
Simplex.Chat.Mobile
Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared
+99 -53
View File
@@ -203,8 +203,9 @@ _defaultSMPServers =
_defaultNtfServers :: [NtfServer]
_defaultNtfServers =
[ "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion",
"ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion"
[ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion"
-- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion",
-- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion"
]
maxImageSize :: Integer
@@ -1299,12 +1300,18 @@ processChatCommand' vr = \case
APIAcceptContact incognito connReqId -> withUser $ \_ -> do
(user@User {userId}, cReq@UserContactRequest {userContactLinkId}) <- withFastStore $ \db -> getContactRequest' db connReqId
withUserContactLock "acceptContact" userContactLinkId $ do
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
ucl <- withFastStore $ \db -> getUserContactLinkById db userId userContactLinkId
let contactUsed = (\(_, groupId_, _) -> isNothing groupId_) ucl
-- [incognito] generate profile to send, create connection with incognito profile
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
ct <- acceptContactRequest user cReq incognitoProfile contactUsed
pure $ CRAcceptingContactRequest user ct
ct' <- withStore' $ \db -> do
deleteContactRequestRec db user cReq
updateContactAccepted db user ct contactUsed
conn' <-
if sqSecured
then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady
else pure conn
pure ct {contactUsed, activeConn = Just conn'}
pure $ CRAcceptingContactRequest user ct'
APIRejectContact connReqId -> withUser $ \user -> do
cReq@UserContactRequest {userContactLinkId, agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <-
withFastStore $ \db ->
@@ -1450,26 +1457,31 @@ processChatCommand' vr = \case
CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a token mode)
APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) >> ok_
APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_
APIGetNtfMessage nonce encNtfInfo -> withUser $ \_ -> do
(NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta}, msg) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo
let agentConnId = AgentConnId ntfConnId
user_ <- withStore' (`getUserByAConnId` agentConnId)
connEntity_ <-
pure user_ $>>= \user ->
withStore (\db -> Just <$> getConnectionEntity db vr user agentConnId) `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing)
pure
CRNtfMessages
{ user_,
connEntity_,
-- Decrypted ntf meta of the expected message (the one notification was sent for)
expectedMsg_ = expectedMsgInfo <$> nMsgMeta,
-- Info of the first message retrieved by agent using GET
-- (may differ from the expected message due to, for example, coalescing or loss of notifications)
receivedMsg_ = receivedMsgInfo <$> msg
}
ApiGetConnNtfMessage (AgentConnId connId) -> withUser $ \_ -> do
msg <- withAgent $ \a -> getConnectionMessage a connId
pure $ CRConnNtfMessage (receivedMsgInfo <$> msg)
APIGetNtfConns nonce encNtfInfo -> withUser $ \user -> do
ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo
(errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos))
unless (null errs) $ toView $ CRChatErrors (Just user) errs
pure $ CRNtfConns ntfMsgs
where
getMsgConn :: DB.Connection -> NotificationInfo -> IO NtfConn
getMsgConn db NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta} = do
let agentConnId = AgentConnId ntfConnId
user_ <- getUserByAConnId db agentConnId
connEntity_ <-
pure user_ $>>= \user ->
eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId)
pure $
NtfConn
{ user_,
connEntity_,
-- Decrypted ntf meta of the expected message (the one notification was sent for)
expectedMsg_ = expectedMsgInfo <$> nMsgMeta
}
ApiGetConnNtfMessages connIds -> withUser $ \_ -> do
let acIds = L.map (\(AgentConnId acId) -> acId) connIds
msgs <- lift $ withAgent' $ \a -> getConnectionMessages a acIds
let ntfMsgs = L.map (\msg -> receivedMsgInfo <$> msg) msgs
pure $ CRConnNtfMessages ntfMsgs
APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do
cfg@ChatConfig {defaultServers} <- asks config
servers <- withFastStore' (`getProtocolServers` user)
@@ -1763,7 +1775,7 @@ processChatCommand' vr = \case
pure conn'
APIConnectPlan userId cReqUri -> withUserId userId $ \user ->
CRConnectionPlan user <$> connectPlan user cReqUri
APIConnect userId incognito (Just (ACR SCMInvitation cReq)) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
APIConnect userId incognito (Just (ACR SCMInvitation cReq@(CRInvitationUri crData e2e))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
subMode <- chatReadVar subscriptionMode
-- [incognito] generate profile to send
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
@@ -1774,10 +1786,28 @@ processChatCommand' vr = \case
Just (agentV, pqSup') -> do
let chatV = agentToChatVersion agentV
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup'
joinPreparedAgentConnection user pccConnId connId cReq dm pqSup' subMode
pure $ CRSentConfirmation user conn
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case
Nothing -> joinNewConn chatV dm
Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing)
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link
| connStatus == ConnPrepared -> do
-- retrying join after error
pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId
joinPreparedConn (aConnId conn) pcc dm
Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent)
where
joinNewConn chatV dm = do
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
pcc <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup'
joinPreparedConn connId pcc dm
joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do
void $ withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
withFastStore' $ \db -> updateConnectionStatusFromTo db pccConnId ConnPrepared ConnJoined
pure $ CRSentConfirmation user pcc {pccConnStatus = ConnJoined}
cReqs =
( CRInvitationUri crData {crScheme = SSSimplex} e2e,
CRInvitationUri crData {crScheme = simplexChat} e2e
)
APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq
APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq
Connect incognito aCReqUri@(Just cReqUri) -> withUser $ \user@User {userId} -> do
@@ -2029,20 +2059,21 @@ processChatCommand' vr = \case
Just Connection {peerChatVRange} -> do
subMode <- chatReadVar subscriptionMode
dm <- encodeConnInfo $ XGrpAcpt membershipMemId
agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff
let chatV = vr `peerConnChatVersion` peerChatVRange
cId <- withFastStore' $ \db -> do
Connection {connId = cId} <- createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode
agentConnId <- case memberConn fromMember of
Nothing -> do
agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff
let chatV = vr `peerConnChatVersion` peerChatVRange
void $ withFastStore' $ \db -> createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode
pure agentConnId
Just conn -> pure $ aConnId conn
withFastStore' $ \db -> do
updateGroupMemberStatus db userId fromMember GSMemAccepted
updateGroupMemberStatus db userId membership GSMemAccepted
pure cId
void (withAgent $ \a -> joinConnection a (aUserId user) (Just agentConnId) True connRequest dm PQSupportOff subMode)
void (withAgent $ \a -> joinConnection a (aUserId user) agentConnId True connRequest dm PQSupportOff subMode)
`catchChatError` \e -> do
withFastStore' $ \db -> do
deleteConnectionRecord db user cId
updateGroupMemberStatus db userId fromMember GSMemInvited
updateGroupMemberStatus db userId membership GSMemInvited
withAgent $ \a -> deleteConnectionAsync a False agentConnId
throwError e
updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CRChatError (Just user))
pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing
@@ -2615,7 +2646,7 @@ processChatCommand' vr = \case
joinPreparedAgentConnection user pccConnId connId cReq dm pqSup subMode
joinPreparedAgentConnection :: User -> Int64 -> ConnId -> ConnectionRequestUri m -> ByteString -> PQSupport -> SubscriptionMode -> CM ()
joinPreparedAgentConnection user pccConnId connId cReq connInfo pqSup subMode = do
void (withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq connInfo pqSup subMode)
void (withAgent $ \a -> joinConnection a (aUserId user) connId True cReq connInfo pqSup subMode)
`catchChatError` \e -> do
withFastStore' $ \db -> deleteConnectionRecord db user pccConnId
withAgent $ \a -> deleteConnectionAsync a False connId
@@ -2857,6 +2888,8 @@ processChatCommand' vr = \case
connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case
Nothing -> pure $ CPInvitationLink ILPOk
Just (RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing) ->
pure $ CPInvitationLink ILPOk
Just (RcvDirectMsgConnection conn ct_) -> do
let Connection {connStatus, contactConnInitiated} = conn
if
@@ -3664,29 +3697,41 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of
liftIO $ B.hPut h "" >> hFlush h
| otherwise = liftIO $ B.writeFile fPath ""
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
acceptContactRequest :: User -> UserContactRequest -> IncognitoEnabled -> CM (Contact, Connection, SndQueueSecured)
acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognito = do
subMode <- chatReadVar subscriptionMode
let pqSup = PQSupportOn
vr <- chatVersionRange
let profileToSend = profileToSendOnAccept user incognitoProfile False
chatV = vr `peerConnChatVersion` cReqChatVRange
pqSup' = pqSup `CR.pqSupportAnd` pqSupport
vr <- chatVersionRange
let chatV = vr `peerConnChatVersion` cReqChatVRange
(ct, conn, incognitoProfile) <- case contactId_ of
Nothing -> do
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
(ct, conn) <- withStore' $ \db -> createAcceptedContact db user connId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' False
pure (ct, conn, incognitoProfile)
Just contactId -> do
ct <- withFastStore $ \db -> getContact db vr user contactId
case contactConn ct of
Nothing -> throwChatError $ CECommandError "contact has no connection"
Just conn@Connection {customUserProfileId} -> do
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
pure (ct, conn, ExistingIncognito <$> incognitoProfile)
let profileToSend = profileToSendOnAccept user incognitoProfile False
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
(acId, sqSecured) <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode
let connStatus = if sqSecured then ConnSndReady else ConnNew
withStore' $ \db -> createAcceptedContact db user acId connStatus chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed
(ct,conn,) <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
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
acceptContactRequestAsync user cReq@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
vr <- chatVersionRange
let chatV = vr `peerConnChatVersion` cReqChatVRange
(cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV
withStore' $ \db -> do
ct@Contact {activeConn} <- createAcceptedContact db user acId ConnNew chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed
forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId
(ct, Connection {connId}) <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed
deleteContactRequestRec db user cReq
setCommandConnId db user cmdId connId
pure ct
acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
@@ -4551,6 +4596,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
lift $ setContactNetworkStatus ct' NSConnected
toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
when (directOrUsed ct') $ do
unless (contactUsed ct') $ withFastStore' $ \db -> updateContactUsed db user ct'
createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing
createFeatureEnabledItems ct'
when (contactConnInitiated conn') $ do
@@ -8021,8 +8067,8 @@ chatCommandP =
"/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP),
"/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP),
"/_ntf delete " *> (APIDeleteToken <$> strP),
"/_ntf message " *> (APIGetNtfMessage <$> strP <* A.space <*> strP),
"/_ntf conn message " *> (ApiGetConnNtfMessage <$> strP),
"/_ntf conns " *> (APIGetNtfConns <$> strP <* A.space <*> strP),
"/_ntf conn messages " *> (ApiGetConnNtfMessages <$> strP),
"/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
"/_join #" *> (APIJoinGroup <$> A.decimal),
"/_member role #" *> (APIMemberRole <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
+18 -5
View File
@@ -330,8 +330,8 @@ data ChatCommand
| APIRegisterToken DeviceToken NotificationsMode
| APIVerifyToken DeviceToken C.CbNonce ByteString
| APIDeleteToken DeviceToken
| APIGetNtfMessage {nonce :: C.CbNonce, encNtfInfo :: ByteString}
| ApiGetConnNtfMessage {connId :: AgentConnId}
| APIGetNtfConns {nonce :: C.CbNonce, encNtfInfo :: ByteString}
| ApiGetConnNtfMessages {connIds :: NonEmpty AgentConnId}
| APIAddMember GroupId ContactId GroupMemberRole
| APIJoinGroup GroupId
| APIMemberRole GroupId GroupMemberId GroupMemberRole
@@ -745,8 +745,8 @@ data ChatResponse
| CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete
| CRNtfTokenStatus {status :: NtfTknStatus}
| CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer}
| CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, expectedMsg_ :: Maybe NtfMsgInfo, receivedMsg_ :: Maybe NtfMsgInfo}
| CRConnNtfMessage {receivedMsg_ :: Maybe NtfMsgInfo}
| CRNtfConns {ntfConns :: [NtfConn]}
| CRConnNtfMessages {receivedMsgs :: NonEmpty (Maybe NtfMsgInfo)}
| CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo}
| CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection}
| CRRemoteHostList {remoteHosts :: [RemoteHostInfo]}
@@ -1010,7 +1010,7 @@ defaultSimpleNetCfg =
smpWebPort = False,
tcpTimeout_ = Nothing,
logTLSErrors = False
}
}
data ContactSubStatus = ContactSubStatus
{ contact :: Contact,
@@ -1063,6 +1063,13 @@ instance FromJSON ComposedMessage where
parseJSON invalid =
JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid)
data NtfConn = NtfConn
{ user_ :: Maybe User,
connEntity_ :: Maybe ConnectionEntity,
expectedMsg_ :: Maybe NtfMsgInfo
}
deriving (Show)
data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime}
deriving (Show)
@@ -1416,6 +1423,10 @@ catchStoreError :: ExceptT StoreError IO a -> (StoreError -> ExceptT StoreError
catchStoreError = catchAllErrors mkStoreError
{-# INLINE catchStoreError #-}
tryStoreError' :: ExceptT StoreError IO a -> IO (Either StoreError a)
tryStoreError' = tryAllErrors' mkStoreError
{-# INLINE tryStoreError' #-}
mkStoreError :: SomeException -> StoreError
mkStoreError = SEInternalError . show
{-# INLINE mkStoreError #-}
@@ -1531,6 +1542,8 @@ $(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary)
$(JQ.deriveJSON defaultJSON ''NtfMsgInfo)
$(JQ.deriveJSON defaultJSON ''NtfConn)
$(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo)
$(JQ.deriveJSON defaultJSON ''SwitchProgress)
@@ -0,0 +1,22 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20241010_contact_requests_contact_id where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20241010_contact_requests_contact_id :: Query
m20241010_contact_requests_contact_id =
[sql|
ALTER TABLE contact_requests ADD COLUMN contact_id INTEGER REFERENCES contacts ON DELETE CASCADE;
CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id);
|]
down_m20241010_contact_requests_contact_id :: Query
down_m20241010_contact_requests_contact_id =
[sql|
DROP INDEX idx_contact_requests_contact_id;
ALTER TABLE contact_requests DROP COLUMN contact_id;
|]
@@ -327,6 +327,7 @@ CREATE TABLE contact_requests(
peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
pq_support INTEGER NOT NULL DEFAULT 0,
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON UPDATE CASCADE
@@ -888,3 +889,4 @@ CREATE INDEX idx_chat_items_fwd_from_chat_item_id ON chat_items(
CREATE INDEX idx_received_probes_group_member_id on received_probes(
group_member_id
);
CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id);
+11
View File
@@ -125,6 +125,17 @@ data ConnectionEntity
$(JQ.deriveJSON (sumTypeJSON fstToLower) ''ConnectionEntity)
connEntityInfo :: ConnectionEntity -> String
connEntityInfo = \case
RcvDirectMsgConnection c ct_ -> ctInfo ct_ <> ", status: " <> show (connStatus c)
RcvGroupMsgConnection c g m -> mInfo g m <> ", status: " <> show (connStatus c)
SndFileConnection c _ft -> "snd file, status: " <> show (connStatus c)
RcvFileConnection c _ft -> "rcv file, status: " <> show (connStatus c)
UserContactConnection c _uc -> "user address, status: " <> show (connStatus c)
where
ctInfo = maybe "connection" $ \Contact {contactId} -> "contact " <> show contactId
mInfo GroupInfo {groupId} GroupMember {groupMemberId} = "group " <> show groupId <> ", member " <> show groupMemberId
updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity
updateEntityConnStatus connEntity connStatus = case connEntity of
RcvDirectMsgConnection c ct_ -> RcvDirectMsgConnection (st c) ((\ct -> (ct :: Contact) {activeConn = Just $ st c}) <$> ct_)
+2 -2
View File
@@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis
-- when acting as host
minRemoteCtrlVersion :: AppVersion
minRemoteCtrlVersion = AppVersion [6, 1, 0, 4]
minRemoteCtrlVersion = AppVersion [6, 1, 0, 8]
-- when acting as controller
minRemoteHostVersion :: AppVersion
minRemoteHostVersion = AppVersion [6, 1, 0, 4]
minRemoteHostVersion = AppVersion [6, 1, 0, 8]
currentAppVersion :: AppVersion
currentAppVersion = AppVersion SC.version
+53 -28
View File
@@ -62,6 +62,8 @@ module Simplex.Chat.Store.Direct
getContactRequestIdByName,
deleteContactRequest,
createAcceptedContact,
deleteContactRequestRec,
updateContactAccepted,
getUserByContactRequestId,
getPendingContactConnections,
updatePCCUser,
@@ -69,6 +71,7 @@ module Simplex.Chat.Store.Direct
getConnectionById,
getConnectionsContacts,
updateConnectionStatus,
updateConnectionStatusFromTo,
updateContactSettings,
setConnConnReqInv,
resetContactConnInitiated,
@@ -655,7 +658,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V
db
[sql|
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
@@ -724,7 +727,7 @@ getContactRequest db User {userId} contactRequestId =
db
[sql|
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
@@ -766,9 +769,8 @@ deleteContactRequest db User {userId} contactRequestId = do
(userId, userId, contactRequestId, userId)
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
createAcceptedContact :: DB.Connection -> User -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connStatus connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do
createdAt <- getCurrentTime
customUserProfileId <- forM incognitoProfile $ \case
NewIncognito p -> createIncognitoProfile_ db userId createdAt p
@@ -779,29 +781,42 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)"
(userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId, contactUsed)
contactId <- insertedRowId db
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connStatus connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName)
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew 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,
updatedAt = createdAt,
chatTs = Just createdAt,
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
uiThemes = Nothing,
chatDeleted = False,
customData = Nothing
}
ct =
Contact
{ contactId,
localDisplayName,
profile = toLocalProfile profileId profile "",
activeConn = Just conn,
viaGroup = Nothing,
contactUsed,
contactStatus = CSActive,
chatSettings = defaultChatSettings,
userPreferences,
mergedPreferences,
createdAt,
updatedAt = createdAt,
chatTs = Just createdAt,
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
uiThemes = Nothing,
chatDeleted = False,
customData = Nothing
}
pure (ct, conn)
deleteContactRequestRec :: DB.Connection -> User -> UserContactRequest -> IO ()
deleteContactRequestRec db User {userId} UserContactRequest {contactRequestId} =
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO ()
updateContactAccepted db User {userId} Contact {contactId} contactUsed =
DB.execute
db
"UPDATE contacts SET contact_used = ? WHERE user_id = ? AND contact_id = ?"
(contactUsed, userId, contactId)
getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64
getContactIdByName db User {userId} cName =
@@ -927,7 +942,17 @@ getConnectionsContacts db agentConnIds = do
toContactRef (contactId, connId, acId, localDisplayName) = ContactRef {contactId, connId, agentConnId = AgentConnId acId, localDisplayName}
updateConnectionStatus :: DB.Connection -> Connection -> ConnStatus -> IO ()
updateConnectionStatus db Connection {connId} connStatus = do
updateConnectionStatus db Connection {connId} = updateConnectionStatus_ db connId
{-# INLINE updateConnectionStatus #-}
updateConnectionStatusFromTo :: DB.Connection -> Int64 -> ConnStatus -> ConnStatus -> IO ()
updateConnectionStatusFromTo db connId fromStatus toStatus = do
maybeFirstRow fromOnly (DB.query db "SELECT conn_status FROM connections WHERE connection_id = ?" (Only connId)) >>= \case
Just status | status == fromStatus -> updateConnectionStatus_ db connId toStatus
_ -> pure ()
updateConnectionStatus_ :: DB.Connection -> Int64 -> ConnStatus -> IO ()
updateConnectionStatus_ db connId connStatus = do
currentTs <- getCurrentTime
if connStatus == ConnReady
then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId)
+5 -5
View File
@@ -1933,8 +1933,8 @@ createMemberContact
contactId <- insertedRowId db
DB.execute
db
"UPDATE group_members SET contact_id = ?, updated_at = ? WHERE group_member_id = ?"
(contactId, currentTs, groupMemberId)
"UPDATE group_members SET contact_id = ?, updated_at = ? WHERE contact_profile_id = ?"
(contactId, currentTs, memberContactProfileId)
DB.execute
db
[sql|
@@ -2003,7 +2003,7 @@ createMemberContactInvited
user@User {userId, profile = LocalProfile {preferences}}
connIds
gInfo
m@GroupMember {groupMemberId, localDisplayName = memberLDN, memberProfile, memberContactProfileId}
m@GroupMember {localDisplayName = memberLDN, memberProfile, memberContactProfileId}
mConn
subMode = do
currentTs <- liftIO getCurrentTime
@@ -2031,8 +2031,8 @@ createMemberContactInvited
contactId <- insertedRowId db
DB.execute
db
"UPDATE group_members SET contact_id = ?, updated_at = ? WHERE group_member_id = ?"
(contactId, currentTs, groupMemberId)
"UPDATE group_members SET contact_id = ?, updated_at = ? WHERE contact_profile_id = ?"
(contactId, currentTs, memberContactProfileId)
pure contactId
updateMemberContactInvited :: DB.Connection -> User -> (CommandId, ConnId) -> GroupInfo -> Connection -> Contact -> SubscriptionMode -> ExceptT StoreError IO Contact
+3 -2
View File
@@ -882,7 +882,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
db
( [sql|
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id,
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences,
cr.created_at, cr.updated_at as ts,
cr.peer_chat_min_version, cr.peer_chat_max_version
@@ -930,6 +930,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
FROM connections
WHERE user_id = :user_id
AND conn_type = :conn_contact
AND conn_status != :conn_status
AND contact_id IS NULL
AND conn_level = 0
AND via_contact IS NULL
@@ -938,7 +939,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
|]
<> pagQuery
)
([":user_id" := userId, ":conn_contact" := ConnContact, ":search" := search] <> pagParams)
([":user_id" := userId, ":conn_contact" := ConnContact, ":conn_status" := ConnPrepared, ":search" := search] <> pagParams)
toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData
toPreview connRow =
let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow
+3 -1
View File
@@ -113,6 +113,7 @@ import Simplex.Chat.Migrations.M20240528_quota_err_counter
import Simplex.Chat.Migrations.M20240827_calls_uuid
import Simplex.Chat.Migrations.M20240920_user_order
import Simplex.Chat.Migrations.M20241008_indexes
import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -225,7 +226,8 @@ schemaMigrations =
("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter),
("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid),
("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order),
("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes)
("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes),
("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id)
]
-- | The list of migrations in ascending order by date
+3 -3
View File
@@ -411,13 +411,13 @@ getProfileById db userId profileId =
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile
toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
toContactRequest :: ContactRequestRow -> UserContactRequest
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do
let profile = Profile {displayName, fullName, image, contactLink, preferences}
cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt}
in UserContactRequest {contactRequestId, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt}
userQuery :: Query
userQuery =
+5
View File
@@ -297,6 +297,7 @@ userContactGroupId UserContact {groupId} = groupId
data UserContactRequest = UserContactRequest
{ contactRequestId :: Int64,
agentInvitationId :: AgentInvId,
contactId_ :: Maybe ContactId,
userContactLinkId :: Int64,
agentContactConnId :: AgentConnId, -- connection id of user contact
cReqChatVRange :: VersionRangeChat,
@@ -1371,6 +1372,8 @@ aConnId' PendingContactConnection {pccAgentConnId = AgentConnId cId} = cId
data ConnStatus
= -- | connection is created by initiating party with agent NEW command (createConnection)
ConnNew
| -- | connection is prepared, to avoid changing keys on invitation links when retrying.
ConnPrepared
| -- | connection is joined by joining party with agent JOIN command (joinConnection)
ConnJoined
| -- | initiating party received CONF notification (to be renamed to REQ)
@@ -1399,6 +1402,7 @@ instance ToJSON ConnStatus where
instance TextEncoding ConnStatus where
textDecode = \case
"new" -> Just ConnNew
"prepared" -> Just ConnPrepared
"joined" -> Just ConnJoined
"requested" -> Just ConnRequested
"accepted" -> Just ConnAccepted
@@ -1408,6 +1412,7 @@ instance TextEncoding ConnStatus where
_ -> Nothing
textEncode = \case
ConnNew -> "new"
ConnPrepared -> "prepared"
ConnJoined -> "joined"
ConnRequested -> "requested"
ConnAccepted -> "accepted"

Some files were not shown because too many files have changed in this diff Show More