Compare commits

...

28 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
78 changed files with 1195 additions and 511 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-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 " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> 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 - name: Unix prepare cabal.project.local for Mac
if: matrix.os == 'macos-13' 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-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 " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> 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 - name: Install AppImage dependencies
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
run: sudo apt install -y desktop-file-utils 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 - name: Install pkg-config for Mac
if: matrix.os == 'macos-latest' || matrix.os == 'macos-13' 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 - name: Unix prepare cabal.project.local for Ubuntu
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04' if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
@@ -322,6 +334,7 @@ jobs:
git git
perl perl
make make
mingw-w64-x86_64-rocksdb
pacboy: >- pacboy: >-
toolchain:p toolchain:p
cmake:p cmake:p
@@ -341,6 +354,10 @@ jobs:
echo " flags: +openssl" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local
echo " extra-include-dirs: $openssl_windows_style_path\include" >> 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 " 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* rm -rf dist-newstyle/src/direct-sq*
sed -i "s/, unix /--, unix /" simplex-chat.cabal 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. 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 ### 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. **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: 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) [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) [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) [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) [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). [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). [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). [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. [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. 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 @EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared @ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared @ObservedObject var callController = CallController.shared
@ObservedObject var appSheetState = AppSheetState.shared
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@EnvironmentObject var sceneDelegate: SceneDelegate @EnvironmentObject var sceneDelegate: SceneDelegate
@@ -250,7 +251,8 @@ struct ContentView: View {
private func mainView() -> some View { private func mainView() -> some View {
ZStack(alignment: .top) { ZStack(alignment: .top) {
ChatListView(activeUserPickerSheet: $chatListUserPickerSheet).privacySensitive(protectScreen) ChatListView(activeUserPickerSheet: $chatListUserPickerSheet)
.redacted(reason: appSheetState.redactionReasons(protectScreen))
.onAppear { .onAppear {
requestNtfAuthorization() requestNtfAuthorization()
// Local Authentication notice is to be shown on next start after onboarding is complete // 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 chatDbEncrypted: Bool?
@Published var chatDbStatus: DBMigrationResult? @Published var chatDbStatus: DBMigrationResult?
@Published var ctrlInitInProgress: Bool = false @Published var ctrlInitInProgress: Bool = false
@Published var notificationResponse: UNNotificationResponse?
// local authentication // local authentication
@Published var contentViewAccessAuthenticated: Bool = false @Published var contentViewAccessAuthenticated: Bool = false
@Published var laRequest: LocalAuthRequest? @Published var laRequest: LocalAuthRequest?
+29 -10
View File
@@ -29,17 +29,33 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
private var granted = false private var granted = false
private var prevNtfTime: Dictionary<ChatId, Date> = [:] private var prevNtfTime: Dictionary<ChatId, Date> = [:]
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
// Handle notification when app is in background // Handle notification when app is in background
func userNotificationCenter(_ center: UNUserNotificationCenter, func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse, didReceive response: UNNotificationResponse,
withCompletionHandler handler: () -> Void) { withCompletionHandler handler: () -> Void) {
logger.debug("NtfManager.userNotificationCenter: didReceive") 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 chatModel = ChatModel.shared
let action = response.actionIdentifier let content = ntfResponse.notification.request.content
logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") let action = ntfResponse.actionIdentifier
logger.debug("NtfManager.processNotificationResponse: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
if let userId = content.userInfo["userId"] as? Int64, if let userId = content.userInfo["userId"] as? Int64,
userId != chatModel.currentUser?.userId { userId != chatModel.currentUser?.userId {
logger.debug("NtfManager.processNotificationResponse changeActiveUser")
changeActiveUser(userId, viewPwd: nil) changeActiveUser(userId, viewPwd: nil)
} }
if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito), if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito),
@@ -61,7 +77,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
ItemsModel.shared.loadOpenChat(chatId) ItemsModel.shared.loadOpenChat(chatId)
} }
} }
handler()
} }
private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? { private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? {
@@ -76,7 +91,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
return nil return nil
} }
// Handle notification when the app is in foreground // Handle notification when the app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification, willPresent notification: UNNotification,
@@ -185,6 +199,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
actions: [], actions: [],
intentIdentifiers: [], intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: NSLocalizedString("SimpleX encrypted message or connection event", comment: "notification") 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) { func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) {
logger.debug("NtfManager.notifyContactRequest") logger.debug("NtfManager.notifyContactRequest")
addNotification(createContactRequestNtf(user, contactRequest)) addNotification(createContactRequestNtf(user, contactRequest, 0))
} }
func notifyContactConnected(_ user: any UserLike, _ contact: Contact) { func notifyContactConnected(_ user: any UserLike, _ contact: Contact) {
logger.debug("NtfManager.notifyContactConnected") logger.debug("NtfManager.notifyContactConnected")
addNotification(createContactConnectedNtf(user, contact)) addNotification(createContactConnectedNtf(user, contact, 0))
} }
func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) { func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) {
logger.debug("NtfManager.notifyMessageReceived") logger.debug("NtfManager.notifyMessageReceived")
if cInfo.ntfsEnabled { if cInfo.ntfsEnabled {
addNotification(createMessageReceivedNtf(user, cInfo, cItem)) addNotification(createMessageReceivedNtf(user, cInfo, cItem, 0))
} }
} }
func notifyCallInvitation(_ invitation: RcvCallInvitation) { func notifyCallInvitation(_ invitation: RcvCallInvitation) {
logger.debug("NtfManager.notifyCallInvitation") logger.debug("NtfManager.notifyCallInvitation")
addNotification(createCallInvitationNtf(invitation)) addNotification(createCallInvitationNtf(invitation, 0))
} }
func setNtfBadgeCount(_ count: Int) { func setNtfBadgeCount(_ count: Int) {
+6
View File
@@ -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." 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) 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))): case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))):
if internalErr == "SEUniqueID" { if internalErr == "SEUniqueID" {
let alert = mkAlert( let alert = mkAlert(
+11 -5
View File
@@ -82,11 +82,17 @@ struct SimpleXApp: App {
if appState != .stopped { if appState != .stopped {
startChatAndActivate { startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true { if chatModel.chatRunning == true {
Task { if let ntfResponse = chatModel.notificationResponse {
await updateChats() chatModel.notificationResponse = nil
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { NtfManager.shared.processNotificationResponse(ntfResponse)
await updateCallInvitations() }
if appState.inactive {
Task {
await updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
} }
} }
} }
@@ -11,11 +11,11 @@ import SimpleXChat
struct CIChatFeatureView: View { struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel @EnvironmentObject var m: ChatModel
@Environment(\.revealed) var revealed: Bool
@ObservedObject var im = ItemsModel.shared @ObservedObject var im = ItemsModel.shared
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
var chatItem: ChatItem var chatItem: ChatItem
@Binding var revealed: Bool
var feature: Feature var feature: Feature
var icon: String? = nil var icon: String? = nil
var iconColor: Color var iconColor: Color
@@ -106,6 +106,9 @@ struct CIChatFeatureView: View {
struct CIChatFeatureView_Previews: PreviewProvider { struct CIChatFeatureView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let enabled = FeatureEnabled(forUser: false, forContact: false) 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 file: nil
) )
Group { Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentFile, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: sentFile)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample())
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(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted))
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: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled))
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(fileSize: 1_000_000_000, fileStatus: .rcvInvitation))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false)) 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), 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))
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile)
} }
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360)) .previewLayout(.fixed(width: 360, height: 360))
} }
} }
@@ -510,10 +510,10 @@ struct CIVoiceView_Previews: PreviewProvider {
duration: 30, duration: 30,
allowMenu: Binding.constant(true) allowMenu: Binding.constant(true)
) )
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true)) ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), 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)), revealed: Binding.constant(false), 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, revealed: Binding.constant(false), allowMenu: .constant(true)) ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, allowMenu: .constant(true))
} }
.previewLayout(.fixed(width: 360, height: 360)) .previewLayout(.fixed(width: 360, height: 360))
} }
@@ -92,12 +92,13 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
file: CIFile.getSample(fileStatus: .sndComplete) file: CIFile.getSample(fileStatus: .sndComplete)
) )
Group { Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false)) 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)), revealed: Binding.constant(false)) 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."), 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."))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote)
} }
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360)) .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 { struct MarkedDeletedItemView: View {
@EnvironmentObject var m: ChatModel @EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
var chatItem: ChatItem var chatItem: ChatItem
@Binding var revealed: Bool
var body: some View { var body: some View {
(Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText) (Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText)
@@ -79,7 +79,10 @@ struct MarkedDeletedItemView: View {
struct MarkedDeletedItemView_Previews: PreviewProvider { struct MarkedDeletedItemView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group { 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)) .previewLayout(.fixed(width: 360, height: 200))
} }
+31 -28
View File
@@ -14,19 +14,28 @@ extension EnvironmentValues {
static let defaultValue: Bool = true static let defaultValue: Bool = true
} }
struct Revealed: EnvironmentKey {
static let defaultValue: Bool = true
}
var showTimestamp: Bool { var showTimestamp: Bool {
get { self[ShowTimestamp.self] } get { self[ShowTimestamp.self] }
set { self[ShowTimestamp.self] = newValue } set { self[ShowTimestamp.self] = newValue }
} }
var revealed: Bool {
get { self[Revealed.self] }
set { self[Revealed.self] = newValue }
}
} }
struct ChatItemView: View { struct ChatItemView: View {
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@Environment(\.showTimestamp) var showTimestamp: Bool @Environment(\.showTimestamp) var showTimestamp: Bool
@Environment(\.revealed) var revealed: Bool
var chatItem: ChatItem var chatItem: ChatItem
var maxWidth: CGFloat = .infinity var maxWidth: CGFloat = .infinity
@Binding var revealed: Bool
@Binding var allowMenu: Bool @Binding var allowMenu: Bool
init( init(
@@ -34,27 +43,25 @@ struct ChatItemView: View {
chatItem: ChatItem, chatItem: ChatItem,
showMember: Bool = false, showMember: Bool = false,
maxWidth: CGFloat = .infinity, maxWidth: CGFloat = .infinity,
revealed: Binding<Bool>,
allowMenu: Binding<Bool> = .constant(false) allowMenu: Binding<Bool> = .constant(false)
) { ) {
self.chat = chat self.chat = chat
self.chatItem = chatItem self.chatItem = chatItem
self.maxWidth = maxWidth self.maxWidth = maxWidth
_revealed = revealed
_allowMenu = allowMenu _allowMenu = allowMenu
} }
var body: some View { var body: some View {
let ci = chatItem let ci = chatItem
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) { 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 { } 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) { if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
EmojiItemView(chat: chat, chatItem: ci) EmojiItemView(chat: chat, chatItem: ci)
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent { } 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) CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu)
} else if ci.content.msgContent == nil { } 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 { } else {
framedItemView() framedItemView()
} }
@@ -84,7 +91,6 @@ struct ChatItemView: View {
chat: chat, chat: chat,
chatItem: chatItem, chatItem: chatItem,
preview: preview, preview: preview,
revealed: $revealed,
maxWidth: maxWidth, maxWidth: maxWidth,
imgWidth: adjustedMaxWidth, imgWidth: adjustedMaxWidth,
videoWidth: adjustedMaxWidth, videoWidth: adjustedMaxWidth,
@@ -96,9 +102,9 @@ struct ChatItemView: View {
struct ChatItemContentView<Content: View>: View { struct ChatItemContentView<Content: View>: View {
@EnvironmentObject var chatModel: ChatModel @EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
var chatItem: ChatItem var chatItem: ChatItem
@Binding var revealed: Bool
var msgContentView: () -> Content var msgContentView: () -> Content
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@@ -130,7 +136,7 @@ struct ChatItemContentView<Content: View>: View {
case let .rcvChatPreference(feature, allowed, param): case let .rcvChatPreference(feature, allowed, param):
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param) CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
case let .sndChatPreference(feature, _, _): 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 .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 .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) 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 { 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? { private var mergedGroupEventText: Text? {
@@ -238,16 +244,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text {
struct ChatItemView_Previews: PreviewProvider { struct ChatItemView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group{ Group{
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false)) 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)), revealed: Binding.constant(false)) 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), revealed: Binding.constant(true)) 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), revealed: Binding.constant(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)) .previewLayout(.fixed(width: 360, height: 70))
.environmentObject(Chat.sampleData) .environmentObject(Chat.sampleData)
} }
@@ -265,8 +272,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)), content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)),
quotedItem: nil, quotedItem: nil,
file: nil file: nil
), )
revealed: Binding.constant(true)
) )
ChatItemView( ChatItemView(
chat: Chat.sampleData, chat: Chat.sampleData,
@@ -276,8 +282,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvDecryptionError(msgDecryptError: .ratchetHeader, msgCount: 2), content: .rcvDecryptionError(msgDecryptError: .ratchetHeader, msgCount: 2),
quotedItem: nil, quotedItem: nil,
file: nil file: nil
), )
revealed: Binding.constant(true)
) )
ChatItemView( ChatItemView(
chat: Chat.sampleData, chat: Chat.sampleData,
@@ -287,8 +292,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin), content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin),
quotedItem: nil, quotedItem: nil,
file: nil file: nil
), )
revealed: Binding.constant(true)
) )
ChatItemView( ChatItemView(
chat: Chat.sampleData, chat: Chat.sampleData,
@@ -298,8 +302,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)), content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)),
quotedItem: nil, quotedItem: nil,
file: nil file: nil
), )
revealed: Binding.constant(true)
) )
ChatItemView( ChatItemView(
chat: Chat.sampleData, chat: Chat.sampleData,
@@ -309,10 +312,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
content: ciFeatureContent, content: ciFeatureContent,
quotedItem: nil, quotedItem: nil,
file: nil file: nil
), )
revealed: Binding.constant(true)
) )
} }
.environment(\.revealed, true)
.previewLayout(.fixed(width: 360, height: 70)) .previewLayout(.fixed(width: 360, height: 70))
.environmentObject(Chat.sampleData) .environmentObject(Chat.sampleData)
} }
+1 -1
View File
@@ -1183,9 +1183,9 @@ struct ChatView: View {
chat: chat, chat: chat,
chatItem: ci, chatItem: ci,
maxWidth: maxWidth, maxWidth: maxWidth,
revealed: .constant(revealed),
allowMenu: $allowMenu allowMenu: $allowMenu
) )
.environment(\.revealed, revealed)
.environment(\.showTimestamp, itemSeparation.timestamp) .environment(\.showTimestamp, itemSeparation.timestamp)
.modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed))) .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed)))
.contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) }
@@ -287,8 +287,8 @@ struct ComposeView: View {
// this is a workaround to fire an explicit event in certain cases // this is a workaround to fire an explicit event in certain cases
@State private var stopPlayback: Bool = false @State private var stopPlayback: Bool = false
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -40,7 +40,7 @@ struct SendMessageView: View {
@State private var showCustomTimePicker = false @State private var showCustomTimePicker = false
@State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get()
@State private var progressByTimeout = false @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 { var body: some View {
ZStack { ZStack {
+1 -1
View File
@@ -78,7 +78,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>( self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
tableView: tableView tableView: tableView
) { (tableView, indexPath, item) -> UITableViewCell? in ) { (tableView, indexPath, item) -> UITableViewCell? in
if indexPath.item > self.itemCount - 8, self.itemCount > 8 { if indexPath.item > self.itemCount - 8 {
self.representer.loadPage() self.representer.loadPage()
} }
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
@@ -121,9 +121,11 @@ struct ChatListView: View {
UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet) UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet)
} }
) )
.sheet(item: $activeUserPickerSheet) { .appSheet(
UserPickerSheetView(sheet: $0) item: $activeUserPickerSheet,
} onDismiss: { chatModel.laRequest = nil },
content: { UserPickerSheetView(sheet: $0) }
)
.onChange(of: activeUserPickerSheet) { .onChange(of: activeUserPickerSheet) {
if $0 != nil { if $0 != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+7 -5
View File
@@ -11,6 +11,12 @@ import SwiftUI
class AppSheetState: ObservableObject { class AppSheetState: ObservableObject {
static let shared = AppSheetState() static let shared = AppSheetState()
@Published var scenePhaseActive: Bool = false @Published var scenePhaseActive: Bool = false
func redactionReasons(_ protectScreen: Bool) -> RedactionReasons {
!protectScreen || scenePhaseActive
? RedactionReasons()
: RedactionReasons.placeholder
}
} }
private struct PrivacySensitive: ViewModifier { private struct PrivacySensitive: ViewModifier {
@@ -19,11 +25,7 @@ private struct PrivacySensitive: ViewModifier {
@ObservedObject var appSheetState: AppSheetState = AppSheetState.shared @ObservedObject var appSheetState: AppSheetState = AppSheetState.shared
func body(content: Content) -> some View { func body(content: Content) -> some View {
if !protectScreen { content.redacted(reason: appSheetState.redactionReasons(protectScreen))
content
} else {
content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy)
}
} }
} }
@@ -16,6 +16,7 @@ struct UserWallpaperEditor: View {
@State var themeModeOverride: ThemeModeOverride @State var themeModeOverride: ThemeModeOverride
@State var applyToMode: DefaultThemeMode? @State var applyToMode: DefaultThemeMode?
@State var showMore: Bool = false @State var showMore: Bool = false
@State var showFileImporter: Bool = false
@Binding var globalThemeUsed: Bool @Binding var globalThemeUsed: Bool
var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void
@@ -125,24 +126,27 @@ struct UserWallpaperEditor: View {
CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) }) CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) })
ImportExportThemeSection(perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes)
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)
}
}
} else { } else {
AdvancedSettingsButton(theme.colors.primary) { showMore = true } 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 { private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool {
@@ -216,6 +220,7 @@ struct ChatWallpaperEditor: View {
@State var themeModeOverride: ThemeModeOverride @State var themeModeOverride: ThemeModeOverride
@State var applyToMode: DefaultThemeMode? @State var applyToMode: DefaultThemeMode?
@State var showMore: Bool = false @State var showMore: Bool = false
@State var showFileImporter: Bool = false
@Binding var globalThemeUsed: Bool @Binding var globalThemeUsed: Bool
var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void
@@ -328,24 +333,27 @@ struct ChatWallpaperEditor: View {
CustomizeThemeColorsSection(editColor: editColor) CustomizeThemeColorsSection(editColor: editColor)
ImportExportThemeSection(perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes)
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)
}
}
} else { } else {
AdvancedSettingsButton(theme.colors.primary) { showMore = true } 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 { 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) .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()) .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 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)) 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 { HStack {
ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: alice)
.modifier(ChatItemClipped(alice, tailVisible: true)) .modifier(ChatItemClipped(alice, tailVisible: true))
Spacer() Spacer()
} }
HStack { HStack {
Spacer() Spacer()
ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false)) ChatItemView(chat: Chat.sampleData, chatItem: bob)
.modifier(ChatItemClipped(bob, tailVisible: true)) .modifier(ChatItemClipped(bob, tailVisible: true))
.frame(alignment: .trailing) .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) ThemeManager.saveAndApplyThemeOverrides(theme)
saveThemeToDatabase(nil) saveThemeToDatabase(nil)
}) }
} )
/// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker() /// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker()
.interactiveDismissDisabled(true) .interactiveDismissDisabled(true)
} }
@@ -595,10 +598,9 @@ struct CustomizeThemeView: View {
struct ImportExportThemeSection: View { struct ImportExportThemeSection: View {
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@Binding var showFileImporter: Bool
var perChat: ThemeModeOverride? var perChat: ThemeModeOverride?
var perUser: ThemeModeOverrides? var perUser: ThemeModeOverrides?
var save: (ThemeOverrides) -> Void
@State private var showFileImporter = false
var body: some View { var body: some View {
Section { Section {
@@ -626,39 +628,47 @@ struct ImportExportThemeSection: View {
} label: { } label: {
Text("Import theme").foregroundColor(theme.colors.primary) Text("Import theme").foregroundColor(theme.colors.primary)
} }
.fileImporter( }
isPresented: $showFileImporter, }
allowedContentTypes: [.data/*.plainText*/], }
allowsMultipleSelection: false
) { result in struct ThemeImporter: ViewModifier {
if case let .success(files) = result, let fileURL = files.first { @Binding var isPresented: Bool
do { var save: (ThemeOverrides) -> Void
var fileSize: Int? = nil
if fileURL.startAccessingSecurityScopedResource() { func body(content: Content) -> some View {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) content.fileImporter(
fileSize = resourceValues.fileSize isPresented: $isPresented,
} allowedContentTypes: [.data/*.plainText*/],
if let fileSize = fileSize, allowsMultipleSelection: false
// Same as Android/desktop ) { result in
fileSize <= 5_500_000 { if case let .success(files) = result, let fileURL = files.first {
if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { do {
save(theme) var fileSize: Int? = nil
logger.error("Saved theme from file") if fileURL.startAccessingSecurityScopedResource() {
} else { let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
logger.error("Error decoding theme file") fileSize = resourceValues.fileSize
}
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)")
} }
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() chatDatabaseRow()
NavigationLink { NavigationLink {
MigrateFromDevice(showProgressOnSettings: $showProgress) 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)) .modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
} label: { } label: {
+358 -138
View File
@@ -26,14 +26,52 @@ enum NSENotification {
case nse(UNMutableNotificationContent) case nse(UNMutableNotificationContent)
case callkit(RcvCallInvitation) case callkit(RcvCallInvitation)
case empty 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 { switch self {
case let .nse(ntf): ntf.categoryIdentifier == ntfCategoryCallInvitation case let .callInvitation(invitation): invitation
case .callkit: true default: nil
case .empty: false }
case .msgInfo: false }
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. // or when background notification is received.
class NSEThreads { class NSEThreads {
static let shared = 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 allThreads: Set<UUID> = []
private var activeThreads: [(UUID, NotificationService)] = [] var activeThreads: [(UUID, NotificationService)] = []
var droppedNotifications: [(ChatId, NSENotificationData)] = []
func newThread() -> UUID { func newThread() -> UUID {
NSEThreads.queue.sync { NSEThreads.queue.sync {
@@ -64,22 +103,19 @@ class NSEThreads {
} }
} }
func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void { func processNotification(_ id: ChatId, _ ntf: NSENotificationData) async -> Void {
var waitTime: Int64 = 5_000_000000 if let (_, nse) = rcvEntityThread(id),
while waitTime > 0 { nse.expectedMessages[id]?.shouldProcessNtf ?? false {
if let (_, nse) = rcvEntityThread(id), nse.processReceivedNtf(id, ntf, signalReady: true)
nse.shouldProcessNtf && nse.processReceivedNtf(ntf) {
break
} else {
try? await Task.sleep(nanoseconds: 10_000000)
waitTime -= 10_000000
}
} }
} }
private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? { private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? {
NSEThreads.queue.sync { 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. // 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 // 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. // 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. // Soon after notification service delivers the last notification it is either suspended or terminated.
class NotificationService: UNNotificationServiceExtension { class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)? 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 var badgeCount: Int = 0
// thread is added to allThreads here - if thread did not start chat, // 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". // chat does not need to be suspended but NSE state still needs to be set to "suspended".
var threadId: UUID? = NSEThreads.shared.newThread() var threadId: UUID? = NSEThreads.shared.newThread()
var notificationInfo: NtfMessages? var expectedMessages: Dictionary<String, ExpectedMessage> = [:] // key is receiveEntityId
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 appSubscriber: AppSubscriber? var appSubscriber: AppSubscriber?
var returnedSuspension = false var returnedSuspension = false
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("DEBUGGING: NotificationService.didReceive") logger.debug("DEBUGGING: NotificationService.didReceive")
let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() }
setBestAttemptNtf(ntf) setServiceBestAttemptNtf(ntf)
self.contentHandler = contentHandler self.contentHandler = contentHandler
registerGroupDefaults() registerGroupDefaults()
let appState = appStateGroupDefault.get() let appState = appStateGroupDefault.get()
@@ -138,13 +181,11 @@ class NotificationService: UNNotificationServiceExtension {
switch appState { switch appState {
case .stopped: case .stopped:
setBadgeCount() setBadgeCount()
setBestAttemptNtf(createAppStoppedNtf()) setServiceBestAttemptNtf(createAppStoppedNtf(badgeCount))
deliverBestAttemptNtf() deliverBestAttemptNtf()
case .suspended: case .suspended:
setBadgeCount()
receiveNtfMessages(request, contentHandler) receiveNtfMessages(request, contentHandler)
case .suspending: case .suspending:
setBadgeCount()
Task { Task {
let state: AppState = await withCheckedContinuation { cont in let state: AppState = await withCheckedContinuation { cont in
appSubscriber = appStateSubscriber { s in appSubscriber = appStateSubscriber { s in
@@ -171,8 +212,9 @@ class NotificationService: UNNotificationServiceExtension {
deliverBestAttemptNtf() deliverBestAttemptNtf()
} }
} }
default: case .active: contentHandler(UNMutableNotificationContent())
deliverBestAttemptNtf() case .activating: contentHandler(UNMutableNotificationContent())
case .bgRefresh: contentHandler(UNMutableNotificationContent())
} }
} }
@@ -192,78 +234,165 @@ class NotificationService: UNNotificationServiceExtension {
if let t = threadId { NSEThreads.shared.startThread(t, self) } if let t = threadId { NSEThreads.shared.startThread(t, self) }
let dbStatus = startChat() let dbStatus = startChat()
if case .ok = dbStatus, if case .ok = dbStatus,
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { let ntfConns = apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo) {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.receivedMsg_ == nil ? 0 : 1))") logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)")
if let connEntity = ntfInfo.connEntity_ {
setBestAttemptNtf( for ntfConn in ntfConns {
ntfInfo.ntfsEnabled addExpectedMessage(ntfConn: ntfConn)
? .nse(createConnectionEventNtf(ntfInfo.user, connEntity)) }
: .empty
) let connIdsToGet = expectedMessages.compactMap { (id, _) in
if let id = connEntity.id, ntfInfo.expectedMsg_ != nil { let started = NSEThreads.queue.sync {
notificationInfo = ntfInfo let canStart = checkCanStart(id)
receiveEntityId = id if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: can start: \(canStart)") }
receiveConnId = connEntity.conn.agentConnId if canStart {
let expectedMsgId = ntfInfo.expectedMsg_?.msgId processDroppedNotifications(id)
let receivedMsgId = ntfInfo.receivedMsg_?.msgId expectedMessages[id]?.startedProcessingNewMsgs = true
logger.debug("NotificationService: receiveNtfMessages: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private), receivedMsgId = \(receivedMsgId ?? "nil", privacy: .private)") expectedMessages[id]?.shouldProcessNtf = true
expectedMessage = expectedMsgId }
shouldProcessNtf = true return canStart
return }
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 { } else if let dbStatus = dbStatus {
setBestAttemptNtf(createErrorNtf(dbStatus)) setServiceBestAttemptNtf(createErrorNtf(dbStatus, badgeCount))
} }
} }
deliverBestAttemptNtf() 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() { override func serviceExtensionTimeWillExpire() {
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire") logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
deliverBestAttemptNtf(urgent: true) deliverBestAttemptNtf(urgent: true)
} }
func processReceivedNtf(_ ntf: NSENotification) -> Bool { var expectingMoreMessages: Bool {
guard let ntfInfo = notificationInfo, let expectedMsgTs = ntfInfo.expectedMsg_?.msgTs else { return false } !expectedMessages.allSatisfy { $0.value.ready }
if !ntfInfo.user.showNotifications { }
self.setBestAttemptNtf(.empty)
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 case let .msgInfo(info) = ntf {
if info.msgId == expectedMessage { if info.msgId == expectedMessage.expectedMsgId {
expectedMessage = nil
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected") logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected")
expectedMessages[id]?.expectedMsgId = nil
if signalReady { entityReady(id) }
self.deliverBestAttemptNtf() self.deliverBestAttemptNtf()
return true
} else if let msgTs = info.msgTs_, msgTs > expectedMsgTs { } 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") 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() self.deliverBestAttemptNtf()
return false } else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let receiveConnId = expectedMessages[id]?.receiveConnId {
} else if allowedGetNextAttempts > 0, let receiveConnId = receiveConnId {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message") logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message")
allowedGetNextAttempts -= 1 expectedMessages[id]?.allowedGetNextAttempts -= 1
if let receivedMsg = apiGetConnNtfMessage(connId: receiveConnId) { if let receivedMsg = getConnNtfMessage(connId: receiveConnId) {
logger.debug("NotificationService processNtf, on apiGetConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)") logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)")
return true
} else { } 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() self.deliverBestAttemptNtf()
return false
} }
} else { } else {
logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unknown message, let other instance to process it") 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() self.deliverBestAttemptNtf()
return false
} }
} else if ntfInfo.user.showNotifications { } else if expectedMessage.ntfConn.user.showNotifications {
logger.debug("NotificationService processNtf: setting best attempt") logger.debug("NotificationService processNtf: setting best attempt")
self.setBestAttemptNtf(ntf) if ntf.notificationEvent != nil {
if ntf.isCallInvitation { setBadgeCount()
self.deliverBestAttemptNtf()
} }
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() { func setBadgeCount() {
@@ -271,37 +400,32 @@ class NotificationService: UNNotificationServiceExtension {
ntfBadgeCountGroupDefault.set(badgeCount) ntfBadgeCountGroupDefault.set(badgeCount)
} }
func setBestAttemptNtf(_ ntf: UNMutableNotificationContent) { func setServiceBestAttemptNtf(_ ntf: UNMutableNotificationContent) {
setBestAttemptNtf(.nse(ntf)) logger.debug("NotificationService.setServiceBestAttemptNtf")
} serviceBestAttemptNtf = .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
}
} }
private func deliverBestAttemptNtf(urgent: Bool = false) { private func deliverBestAttemptNtf(urgent: Bool = false) {
logger.debug("NotificationService.deliverBestAttemptNtf") if (urgent || !expectingMoreMessages) {
// stop processing other messages logger.debug("NotificationService.deliverBestAttemptNtf")
shouldProcessNtf = false // stop processing other messages
for (key, _) in expectedMessages {
expectedMessages[key]?.shouldProcessNtf = false
}
let suspend: Bool let suspend: Bool
if let t = threadId { if let t = threadId {
threadId = nil threadId = nil
suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads
} else { } else {
suspend = false suspend = false
}
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
} }
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
} }
private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) { 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") logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit")
if urgent { if urgent {
// suspending NSE even though there may be other notifications // suspending NSE even though there may be other notifications
@@ -339,19 +463,13 @@ class NotificationService: UNNotificationServiceExtension {
} }
private func deliverNotification() { private func deliverNotification() {
if let handler = contentHandler, let ntf = bestAttemptNtf { if let handler = contentHandler, let ntf = prepareNotification() {
contentHandler = nil contentHandler = nil
bestAttemptNtf = nil serviceBestAttemptNtf = nil
let deliver: (UNMutableNotificationContent?) -> Void = { ntf in
let useNtf = if let ntf = ntf {
appStateGroupDefault.get().running ? UNMutableNotificationContent() : ntf
} else {
UNMutableNotificationContent()
}
handler(useNtf)
}
switch ntf { switch ntf {
case let .nse(content): deliver(content) case let .nse(content):
content.badge = badgeCount as NSNumber
handler(content)
case let .callkit(invitation): case let .callkit(invitation):
logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)") logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)")
CXProvider.reportNewIncomingVoIPPushPayload([ CXProvider.reportNewIncomingVoIPPushPayload([
@@ -362,13 +480,85 @@ class NotificationService: UNNotificationServiceExtension {
"callTs": invitation.callTs.timeIntervalSince1970 "callTs": invitation.callTs.timeIntervalSince1970
]) { error in ]) { error in
logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") 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 .empty:
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo 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 // 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 let isInChina = SKStorefront().countryCode == "CHN"
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } 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)") logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
switch res { switch res {
case let .contactConnected(user, contact, _): case let .contactConnected(user, contact, _):
return (contact.id, .nse(createContactConnectedNtf(user, contact))) return (contact.id, .contactConnected(user, contact))
// case let .contactConnecting(contact): // case let .contactConnecting(contact):
// TODO profile update // TODO profile update
case let .receivedContactRequest(user, contactRequest): 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): case let .newChatItems(user, chatItems):
// Received items are created one at a time // Received items are created one at a time
if let chatItem = chatItems.first { if let chatItem = chatItems.first {
let cInfo = chatItem.chatInfo let cInfo = chatItem.chatInfo
var cItem = chatItem.chatItem var cItem = chatItem.chatItem
if !cInfo.ntfsEnabled {
ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1))
}
if let file = cItem.autoReceiveFile() { if let file = cItem.autoReceiveFile() {
cItem = autoReceiveFile(file) ?? cItem cItem = autoReceiveFile(file) ?? cItem
} }
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty let ntf: NSENotificationData = (cInfo.ntfsEnabled && cItem.showNotification) ? .messageReceived(user, cInfo, cItem) : .noNtf
return cItem.showNotification ? (chatItem.chatId, ntf) : nil return (chatItem.chatId, ntf)
} else { } else {
return nil return nil
} }
@@ -620,10 +807,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
return nil return nil
case let .callInvitation(invitation): case let .callInvitation(invitation):
// Do not post it without CallKit support, iOS will stop launching the app without showing CallKit // Do not post it without CallKit support, iOS will stop launching the app without showing CallKit
return ( return (invitation.contact.id, .callInvitation(invitation))
invitation.contact.id,
useCallKit() ? .callkit(invitation) : .nse(createCallInvitationNtf(invitation))
)
case let .ntfMessage(_, connEntity, ntfMessage): case let .ntfMessage(_, connEntity, ntfMessage):
return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil } return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil }
case .chatSuspended: case .chatSuspended:
@@ -704,15 +888,15 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws {
throw r throw r
} }
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? {
guard apiGetActiveUser() != nil else { guard apiGetActiveUser() != nil else {
logger.debug("no active user") logger.debug("no active user")
return nil return nil
} }
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo))
if case let .ntfMessages(user, connEntity_, expectedMsg_, receivedMsg_) = r, let user = user { if case let .ntfConns(ntfConns) = r {
logger.debug("apiGetNtfMessage response ntfMessages: \(receivedMsg_ == nil ? 0 : 1)") logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)")
return NtfMessages(user: user, connEntity_: connEntity_, expectedMsg_: expectedMsg_, receivedMsg_: receivedMsg_) return ntfConns.compactMap { toUserNtfConn($0) }
} else if case let .chatCmdError(_, error) = r { } else if case let .chatCmdError(_, error) = r {
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
} else { } else {
@@ -721,17 +905,33 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
return nil 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 { guard apiGetActiveUser() != nil else {
logger.debug("no active user") logger.debug("no active user")
return nil return nil
} }
let r = sendSimpleXCmd(.apiGetConnNtfMessage(connId: connId)) let r = sendSimpleXCmd(.apiGetConnNtfMessages(connIds: connIds))
if case let .connNtfMessage(receivedMsg_) = r { if case let .connNtfMessages(receivedMsgs) = r {
logger.debug("apiGetConnNtfMessage response receivedMsg_: \(receivedMsg_ == nil ? 0 : 1)") logger.debug("apiGetConnNtfMessages response receivedMsgs: \(receivedMsgs.count)")
return receivedMsg_ 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 return nil
} }
@@ -769,13 +969,33 @@ func setNetworkConfig(_ cfg: NetCfg) throws {
throw r throw r
} }
struct NtfMessages { struct UserNtfConn {
var user: User var user: User
var connEntity_: ConnectionEntity? var connEntity_: ConnectionEntity?
var expectedMsg_: NtfMsgInfo? var expectedMsg_: NtfMsgInfo?
var receivedMsg_: NtfMsgInfo?
var ntfsEnabled: Bool { var defaultBestAttemptNtf: NSENotificationData {
user.showNotifications && (connEntity_?.ntfsEnabled ?? false) 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
@@ -148,6 +148,11 @@
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.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 */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; }; 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; };
@@ -204,6 +209,7 @@
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; };
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; };
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.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 */; }; CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; };
CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; };
CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; };
@@ -223,11 +229,6 @@
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; };
E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C42CBA891A00D7A2FA /* libgmpxx.a */; };
E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C52CBA891A00D7A2FA /* libgmp.a */; };
E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */; };
E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C72CBA891A00D7A2FA /* libffi.a */; };
E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -490,6 +491,11 @@
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
@@ -545,6 +551,7 @@
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -612,11 +619,6 @@
E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5E997C42CBA891A00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
E5E997C52CBA891A00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a"; sourceTree = "<group>"; };
E5E997C72CBA891A00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -655,14 +657,14 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */,
E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */, 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */,
E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a 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; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -739,11 +741,6 @@
5C764E5C279C70B7000C6508 /* Libraries */ = { 5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E5E997C72CBA891A00D7A2FA /* libffi.a */,
E5E997C52CBA891A00D7A2FA /* libgmp.a */,
E5E997C42CBA891A00D7A2FA /* libgmpxx.a */,
E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */,
E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */,
); );
path = Libraries; path = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -802,6 +799,7 @@
CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */,
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */,
CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */, CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */,
CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -814,6 +812,11 @@
5CC2C0FA2809BF11000C35E3 /* Localizable.strings */, 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */,
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */, 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */,
5C764E5C279C70B7000C6508 /* Libraries */, 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 */, 5CA059C2279559F40002BEB4 /* Shared */,
5CDCAD462818589900503DA2 /* SimpleX NSE */, 5CDCAD462818589900503DA2 /* SimpleX NSE */,
CEE723A82C3BD3D70009AE93 /* SimpleX SE */, CEE723A82C3BD3D70009AE93 /* SimpleX SE */,
@@ -1482,6 +1485,7 @@
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */,
CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */,
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
@@ -1899,7 +1903,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1924,7 +1928,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES_THIN; LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1948,7 +1952,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1973,7 +1977,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1989,11 +1993,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2009,11 +2013,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2034,7 +2038,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s; GCC_OPTIMIZATION_LEVEL = s;
@@ -2049,7 +2053,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2071,7 +2075,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO; ENABLE_CODE_COVERAGE = NO;
@@ -2086,7 +2090,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2108,7 +2112,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2134,7 +2138,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2159,7 +2163,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2185,7 +2189,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2210,7 +2214,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2225,7 +2229,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2244,7 +2248,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 244; CURRENT_PROJECT_VERSION = 245;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2259,7 +2263,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.1; MARKETING_VERSION = 6.1.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
+13 -12
View File
@@ -55,8 +55,8 @@ public enum ChatCommand {
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String) case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
case apiDeleteToken(token: DeviceToken) case apiDeleteToken(token: DeviceToken)
case apiGetNtfMessage(nonce: String, encNtfInfo: String) case apiGetNtfConns(nonce: String, encNtfInfo: String)
case apiGetConnNtfMessage(connId: String) case apiGetConnNtfMessages(connIds: [String])
case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile)
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
case apiJoinGroup(groupId: Int64) 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 .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 .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)"
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)" case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)"
case let .apiGetConnNtfMessage(connId): return "/_ntf conn message \(connId)" 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 .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 .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
case let .apiJoinGroup(groupId): return "/_join #\(groupId)" case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
@@ -369,8 +369,8 @@ public enum ChatCommand {
case .apiRegisterToken: return "apiRegisterToken" case .apiRegisterToken: return "apiRegisterToken"
case .apiVerifyToken: return "apiVerifyToken" case .apiVerifyToken: return "apiVerifyToken"
case .apiDeleteToken: return "apiDeleteToken" case .apiDeleteToken: return "apiDeleteToken"
case .apiGetNtfMessage: return "apiGetNtfMessage" case .apiGetNtfConns: return "apiGetNtfConns"
case .apiGetConnNtfMessage: return "apiGetConnNtfMessage" case .apiGetConnNtfMessages: return "apiGetConnNtfMessages"
case .apiNewGroup: return "apiNewGroup" case .apiNewGroup: return "apiNewGroup"
case .apiAddMember: return "apiAddMember" case .apiAddMember: return "apiAddMember"
case .apiJoinGroup: return "apiJoinGroup" case .apiJoinGroup: return "apiJoinGroup"
@@ -682,8 +682,8 @@ public enum ChatResponse: Decodable, Error {
case callInvitations(callInvitations: [RcvCallInvitation]) case callInvitations(callInvitations: [RcvCallInvitation])
case ntfTokenStatus(status: NtfTknStatus) case ntfTokenStatus(status: NtfTknStatus)
case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String)
case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, expectedMsg_: NtfMsgInfo?, receivedMsg_: NtfMsgInfo?) case ntfConns(ntfConns: [NtfConn])
case connNtfMessage(receivedMsg_: NtfMsgInfo?) case connNtfMessages(receivedMsgs: [NtfMsgInfo?])
case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case contactDisabled(user: UserRef, contact: Contact) case contactDisabled(user: UserRef, contact: Contact)
@@ -851,8 +851,8 @@ public enum ChatResponse: Decodable, Error {
case .callInvitations: return "callInvitations" case .callInvitations: return "callInvitations"
case .ntfTokenStatus: return "ntfTokenStatus" case .ntfTokenStatus: return "ntfTokenStatus"
case .ntfToken: return "ntfToken" case .ntfToken: return "ntfToken"
case .ntfMessages: return "ntfMessages" case .ntfConns: return "ntfConns"
case .connNtfMessage: return "connNtfMessage" case .connNtfMessages: return "connNtfMessages"
case .ntfMessage: return "ntfMessage" case .ntfMessage: return "ntfMessage"
case .contactConnectionDeleted: return "contactConnectionDeleted" case .contactConnectionDeleted: return "contactConnectionDeleted"
case .contactDisabled: return "contactDisabled" case .contactDisabled: return "contactDisabled"
@@ -1029,8 +1029,8 @@ public enum ChatResponse: Decodable, Error {
case let .callInvitations(invs): return String(describing: invs) case let .callInvitations(invs): return String(describing: invs)
case let .ntfTokenStatus(status): return String(describing: status) 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 .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 .ntfConns(ntfConns): return String(describing: ntfConns)
case let .connNtfMessage(receivedMsg_): return "receivedMsg_: \(String(describing: receivedMsg_))" 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 .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 .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) case let .contactDisabled(u, contact): return withUser(u, String(describing: contact))
@@ -2088,6 +2088,7 @@ public enum ProtocolErrorType: Decodable, Hashable {
case AUTH case AUTH
case CRYPTO case CRYPTO
case QUOTA case QUOTA
case STORE(storeErr: String)
case NO_MSG case NO_MSG
case LARGE_MSG case LARGE_MSG
case EXPIRED case EXPIRED
+6 -9
View File
@@ -2270,16 +2270,13 @@ public enum ConnectionEntity: Decodable, Hashable {
case let .userContactConnection(entityConnection, _): entityConnection 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 { 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 ntfCategoryMessageReceived = "NTF_CAT_MESSAGE_RECEIVED"
public let ntfCategoryCallInvitation = "NTF_CAT_CALL_INVITATION" public let ntfCategoryCallInvitation = "NTF_CAT_CALL_INVITATION"
public let ntfCategoryConnectionEvent = "NTF_CAT_CONNECTION_EVENT" public let ntfCategoryConnectionEvent = "NTF_CAT_CONNECTION_EVENT"
public let ntfCategoryManyEvents = "NTF_CAT_MANY_EVENTS"
public let ntfCategoryCheckMessage = "NTF_CAT_CHECK_MESSAGE" public let ntfCategoryCheckMessage = "NTF_CAT_CHECK_MESSAGE"
public let appNotificationId = "chat.simplex.app.notification" public let appNotificationId = "chat.simplex.app.notification"
let contactHidden = NSLocalizedString("Contact hidden:", comment: "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 let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification( return createNotification(
categoryIdentifier: ntfCategoryContactRequest, categoryIdentifier: ntfCategoryContactRequest,
@@ -34,11 +35,12 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName
), ),
targetContentIdentifier: nil, 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 let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification( return createNotification(
categoryIdentifier: ntfCategoryContactConnected, categoryIdentifier: ntfCategoryContactConnected,
@@ -51,12 +53,13 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact)
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName
), ),
targetContentIdentifier: contact.id, targetContentIdentifier: contact.id,
userInfo: ["userId": user.userId] userInfo: ["userId": user.userId],
// userInfo: ["chatId": contact.id, "contactId": contact.apiId] // 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() let previewMode = ntfPreviewModeGroupDefault.get()
var title: String var title: String
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir { 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, title: title,
body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"), body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"),
targetContentIdentifier: cInfo.id, targetContentIdentifier: cInfo.id,
userInfo: ["userId": user.userId] userInfo: ["userId": user.userId],
// userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id] // 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 let text = invitation.callType.media == .video
? NSLocalizedString("Incoming video call", comment: "notification") ? NSLocalizedString("Incoming video call", comment: "notification")
: NSLocalizedString("Incoming audio 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):", title: hideContent ? contactHidden : "\(invitation.contact.chatViewName):",
body: text, body: text,
targetContentIdentifier: nil, 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 let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
var title: String var title: String
var body: String? = nil var body: String? = nil
@@ -118,11 +123,12 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit
title: title, title: title,
body: body, body: body,
targetContentIdentifier: targetContentIdentifier, 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 var title: String
switch dbStatus { switch dbStatus {
case .errorNotADatabase: case .errorNotADatabase:
@@ -142,14 +148,16 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificati
} }
return createNotification( return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent, categoryIdentifier: ntfCategoryConnectionEvent,
title: title title: title,
badgeCount: badgeCount
) )
} }
public func createAppStoppedNtf() -> UNMutableNotificationContent { public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent {
return createNotification( return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent, 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):" : "#\(groupInfo.displayName) \(groupMember.chatViewName):"
} }
public func createNotification(categoryIdentifier: String, title: String, subtitle: String? = nil, body: String? = nil, public func createNotification(
targetContentIdentifier: String? = nil, userInfo: [AnyHashable : Any] = [:]) -> UNMutableNotificationContent { categoryIdentifier: String,
title: String,
subtitle: String? = nil,
body: String? = nil,
targetContentIdentifier: String? = nil,
userInfo: [AnyHashable : Any] = [:],
badgeCount: Int
) -> UNMutableNotificationContent {
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
content.categoryIdentifier = categoryIdentifier content.categoryIdentifier = categoryIdentifier
content.title = title content.title = title
@@ -170,6 +185,7 @@ public func createNotification(categoryIdentifier: String, title: String, subtit
content.userInfo = userInfo content.userInfo = userInfo
// TODO move logic of adding sound here, so it applies to background notifications too // TODO move logic of adding sound here, so it applies to background notifications too
content.sound = .default content.sound = .default
content.badge = badgeCount as NSNumber
// content.interruptionLevel = .active // content.interruptionLevel = .active
// content.relevanceScore = 0.5 // 0-1 // content.relevanceScore = 0.5 // 0-1
return content return content
@@ -1226,6 +1226,15 @@ object ChatController {
) )
return null 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 -> { else -> {
if (!(networkErrorAlert(r))) { if (!(networkErrorAlert(r))) {
apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r) apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r)
@@ -6045,6 +6054,7 @@ sealed class SMPErrorType {
is AUTH -> "AUTH" is AUTH -> "AUTH"
is CRYPTO -> "CRYPTO" is CRYPTO -> "CRYPTO"
is QUOTA -> "QUOTA" is QUOTA -> "QUOTA"
is STORE -> "STORE ${storeErr}"
is NO_MSG -> "NO_MSG" is NO_MSG -> "NO_MSG"
is LARGE_MSG -> "LARGE_MSG" is LARGE_MSG -> "LARGE_MSG"
is EXPIRED -> "EXPIRED" is EXPIRED -> "EXPIRED"
@@ -6057,6 +6067,7 @@ sealed class SMPErrorType {
@Serializable @SerialName("AUTH") class AUTH: SMPErrorType() @Serializable @SerialName("AUTH") class AUTH: SMPErrorType()
@Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType() @Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType()
@Serializable @SerialName("QUOTA") class QUOTA: 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("NO_MSG") class NO_MSG: SMPErrorType()
@Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType() @Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType()
@Serializable @SerialName("EXPIRED") class EXPIRED: 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="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">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_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="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="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
<string name="error_deleting_contact">Error deleting contact</string> <string name="error_deleting_contact">Error deleting contact</string>
+4 -4
View File
@@ -26,11 +26,11 @@ android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11 kotlin.jvm.target=11
android.version_name=6.1 android.version_name=6.1.1
android.version_code=247 android.version_code=249
desktop.version_name=6.1 desktop.version_name=6.1.1
desktop.version_code=73 desktop.version_code=74
kotlin.version=1.9.23 kotlin.version=1.9.23
gradle.plugin.version=8.2.0 gradle.plugin.version=8.2.0
@@ -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). 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 ### Medium severity issues
@@ -2,10 +2,9 @@
layout: layouts/article.html layout: layouts/article.html
title: "SimpleX network: cryptographic design review by Trail of Bits, v6.1 released with better calls and user experience." title: "SimpleX network: cryptographic design review by Trail of Bits, v6.1 released with better calls and user experience."
date: 2024-10-14 date: 2024-10-14
# image: images/20240814-reachable.png image: images/20221108-trail-of-bits.jpg
# previewBody: blog_previews/20240814.html previewBody: blog_previews/20241014.html
permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html" permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"
draft: true
--- ---
@@ -13,6 +12,181 @@ draft: true
**Published:** Oct 14, 2024 **Published:** Oct 14, 2024
This is the placeholder for the security review and release announcement. [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.
Come back on Monday afternoon! [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 # 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) 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. 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 source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5 tag: 0b4492879fd36f0b517dca8f5e36dba187bba686
source-repository-package source-repository-package
type: git 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. 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 ## Reporting security issues
+4 -1
View File
@@ -6,7 +6,7 @@ revision: 16.07.2024
# Transparency Reports # 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. 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) - [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) - [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) - [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). 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 ### 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 ### Messages to manage groups and add members
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplex-chat name: simplex-chat
version: 6.1.0.9 version: 6.1.1.0
#synopsis: #synopsis:
#description: #description:
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme
+4
View File
@@ -11,4 +11,8 @@ package direct-sqlcipher
extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib
flags: +openssl 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 test-show-details: direct
@@ -38,6 +38,30 @@
</description> </description>
<releases> <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"> <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> <url type="details">https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html</url>
<description> <description>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5" = "1awqy4srdgcwmjf7q3s9w75w6wp38qk65fza2k3q1a1s2lj6h8w1"; "https://github.com/simplex-chat/simplexmq.git"."0b4492879fd36f0b517dca8f5e36dba187bba686" = "0rrkq84vx9n6rq67v0awfgnwpq3mmyx3rdzg6k9xwir1397bhf7r";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "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/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+1 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: simplex-chat name: simplex-chat
version: 6.1.0.9 version: 6.1.1.0
category: Web, System, Services, Cryptography category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat author: simplex.chat
+32 -25
View File
@@ -203,8 +203,9 @@ _defaultSMPServers =
_defaultNtfServers :: [NtfServer] _defaultNtfServers :: [NtfServer]
_defaultNtfServers = _defaultNtfServers =
[ "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", [ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion"
"ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" -- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion",
-- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion"
] ]
maxImageSize :: Integer maxImageSize :: Integer
@@ -1456,26 +1457,31 @@ processChatCommand' vr = \case
CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a token mode) CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a token mode)
APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) >> ok_ APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) >> ok_
APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_ APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_
APIGetNtfMessage nonce encNtfInfo -> withUser $ \_ -> do APIGetNtfConns nonce encNtfInfo -> withUser $ \user -> do
(NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta}, msg) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo
let agentConnId = AgentConnId ntfConnId (errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos))
user_ <- withStore' (`getUserByAConnId` agentConnId) unless (null errs) $ toView $ CRChatErrors (Just user) errs
connEntity_ <- pure $ CRNtfConns ntfMsgs
pure user_ $>>= \user -> where
withStore (\db -> Just <$> getConnectionEntity db vr user agentConnId) `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing) getMsgConn :: DB.Connection -> NotificationInfo -> IO NtfConn
pure getMsgConn db NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta} = do
CRNtfMessages let agentConnId = AgentConnId ntfConnId
{ user_, user_ <- getUserByAConnId db agentConnId
connEntity_, connEntity_ <-
-- Decrypted ntf meta of the expected message (the one notification was sent for) pure user_ $>>= \user ->
expectedMsg_ = expectedMsgInfo <$> nMsgMeta, eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId)
-- Info of the first message retrieved by agent using GET pure $
-- (may differ from the expected message due to, for example, coalescing or loss of notifications) NtfConn
receivedMsg_ = receivedMsgInfo <$> msg { user_,
} connEntity_,
ApiGetConnNtfMessage (AgentConnId connId) -> withUser $ \_ -> do -- Decrypted ntf meta of the expected message (the one notification was sent for)
msg <- withAgent $ \a -> getConnectionMessage a connId expectedMsg_ = expectedMsgInfo <$> nMsgMeta
pure $ CRConnNtfMessage (receivedMsgInfo <$> msg) }
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 APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do
cfg@ChatConfig {defaultServers} <- asks config cfg@ChatConfig {defaultServers} <- asks config
servers <- withFastStore' (`getProtocolServers` user) servers <- withFastStore' (`getProtocolServers` user)
@@ -1784,7 +1790,8 @@ processChatCommand' vr = \case
Nothing -> joinNewConn chatV dm Nothing -> joinNewConn chatV dm
Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing) Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing)
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link
| connStatus == ConnPrepared -> do -- retrying join after error | connStatus == ConnPrepared -> do
-- retrying join after error
pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId
joinPreparedConn (aConnId conn) pcc dm joinPreparedConn (aConnId conn) pcc dm
Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent) Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent)
@@ -8060,8 +8067,8 @@ chatCommandP =
"/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP), "/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP),
"/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP), "/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP),
"/_ntf delete " *> (APIDeleteToken <$> strP), "/_ntf delete " *> (APIDeleteToken <$> strP),
"/_ntf message " *> (APIGetNtfMessage <$> strP <* A.space <*> strP), "/_ntf conns " *> (APIGetNtfConns <$> strP <* A.space <*> strP),
"/_ntf conn message " *> (ApiGetConnNtfMessage <$> strP), "/_ntf conn messages " *> (ApiGetConnNtfMessages <$> strP),
"/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole), "/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
"/_join #" *> (APIJoinGroup <$> A.decimal), "/_join #" *> (APIJoinGroup <$> A.decimal),
"/_member role #" *> (APIMemberRole <$> A.decimal <* A.space <*> A.decimal <*> memberRole), "/_member role #" *> (APIMemberRole <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
+14 -5
View File
@@ -330,8 +330,8 @@ data ChatCommand
| APIRegisterToken DeviceToken NotificationsMode | APIRegisterToken DeviceToken NotificationsMode
| APIVerifyToken DeviceToken C.CbNonce ByteString | APIVerifyToken DeviceToken C.CbNonce ByteString
| APIDeleteToken DeviceToken | APIDeleteToken DeviceToken
| APIGetNtfMessage {nonce :: C.CbNonce, encNtfInfo :: ByteString} | APIGetNtfConns {nonce :: C.CbNonce, encNtfInfo :: ByteString}
| ApiGetConnNtfMessage {connId :: AgentConnId} | ApiGetConnNtfMessages {connIds :: NonEmpty AgentConnId}
| APIAddMember GroupId ContactId GroupMemberRole | APIAddMember GroupId ContactId GroupMemberRole
| APIJoinGroup GroupId | APIJoinGroup GroupId
| APIMemberRole GroupId GroupMemberId GroupMemberRole | APIMemberRole GroupId GroupMemberId GroupMemberRole
@@ -745,8 +745,8 @@ data ChatResponse
| CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete | CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete
| CRNtfTokenStatus {status :: NtfTknStatus} | CRNtfTokenStatus {status :: NtfTknStatus}
| CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer}
| CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, expectedMsg_ :: Maybe NtfMsgInfo, receivedMsg_ :: Maybe NtfMsgInfo} | CRNtfConns {ntfConns :: [NtfConn]}
| CRConnNtfMessage {receivedMsg_ :: Maybe NtfMsgInfo} | CRConnNtfMessages {receivedMsgs :: NonEmpty (Maybe NtfMsgInfo)}
| CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo} | CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo}
| CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection}
| CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]}
@@ -1010,7 +1010,7 @@ defaultSimpleNetCfg =
smpWebPort = False, smpWebPort = False,
tcpTimeout_ = Nothing, tcpTimeout_ = Nothing,
logTLSErrors = False logTLSErrors = False
} }
data ContactSubStatus = ContactSubStatus data ContactSubStatus = ContactSubStatus
{ contact :: Contact, { contact :: Contact,
@@ -1063,6 +1063,13 @@ instance FromJSON ComposedMessage where
parseJSON invalid = parseJSON invalid =
JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" 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} data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime}
deriving (Show) deriving (Show)
@@ -1535,6 +1542,8 @@ $(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary)
$(JQ.deriveJSON defaultJSON ''NtfMsgInfo) $(JQ.deriveJSON defaultJSON ''NtfMsgInfo)
$(JQ.deriveJSON defaultJSON ''NtfConn)
$(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo) $(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo)
$(JQ.deriveJSON defaultJSON ''SwitchProgress) $(JQ.deriveJSON defaultJSON ''SwitchProgress)
+2 -2
View File
@@ -325,8 +325,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"] CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"]
CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)]
CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv] CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv]
CRNtfMessages {} -> [] CRNtfConns {} -> []
CRConnNtfMessage {} -> [] CRConnNtfMessages {} -> []
CRNtfMessage {} -> [] CRNtfMessage {} -> []
CRCurrentRemoteHost rhi_ -> CRCurrentRemoteHost rhi_ ->
[ maybe [ maybe
+4
View File
@@ -51,6 +51,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR
import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion) import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion)
import Simplex.Messaging.Server (runSMPServerBlocking) import Simplex.Messaging.Server (runSMPServerBlocking)
import Simplex.Messaging.Server.Env.STM import Simplex.Messaging.Server.Env.STM
import Simplex.Messaging.Server.MsgStore.Types (AMSType (..), SMSType (..))
import Simplex.Messaging.Transport import Simplex.Messaging.Transport
import Simplex.Messaging.Transport.Server (ServerCredentials (..), defaultTransportServerConfig) import Simplex.Messaging.Transport.Server (ServerCredentials (..), defaultTransportServerConfig)
import Simplex.Messaging.Version import Simplex.Messaging.Version
@@ -423,7 +424,10 @@ smpServerCfg =
{ transports = [(serverPort, transport @TLS, False)], { transports = [(serverPort, transport @TLS, False)],
tbqSize = 1, tbqSize = 1,
-- serverTbqSize = 1, -- serverTbqSize = 1,
msgStoreType = AMSType SMSMemory,
msgQueueQuota = 16, msgQueueQuota = 16,
maxJournalMsgCount = 16,
maxJournalStateLines = 16,
queueIdBytes = 12, queueIdBytes = 12,
msgIdBytes = 6, msgIdBytes = 6,
storeLogFile = Nothing, storeLogFile = Nothing,
+2 -3
View File
@@ -245,8 +245,7 @@
"f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>تصدير</a> قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>تصدير</a> قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.",
"comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق",
"hero-overlay-3-title": "التقييم الأمني", "hero-overlay-3-title": "التقييم الأمني",
"hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022.", "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">اقرأ المزيد في الإعلان</a>.",
"hero-overlay-card-3-p-3": "اقرأ المزيد في <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">الإعلان</a>.",
"jobs": "انضم للفريق", "jobs": "انضم للفريق",
"hero-overlay-3-textlink": "التقييم الأمني", "hero-overlay-3-textlink": "التقييم الأمني",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "الشفافية", "docs-dropdown-10": "الشفافية",
"docs-dropdown-11": "الأسئلة الأكثر شيوعًا", "docs-dropdown-11": "الأسئلة الأكثر شيوعًا",
"docs-dropdown-12": "الأمان" "docs-dropdown-12": "الأمان"
} }
+2 -3
View File
@@ -250,10 +250,9 @@
"stable-versions-built-by-f-droid-org": "Stabilní verze vytvořené F-Droid.org", "stable-versions-built-by-f-droid-org": "Stabilní verze vytvořené F-Droid.org",
"releases-to-this-repo-are-done-1-2-days-later": "Vydání v tomto repozitáři se provádí o několik dní později", "releases-to-this-repo-are-done-1-2-days-later": "Vydání v tomto repozitáři se provádí o několik dní později",
"jobs": "Připojit k týmu", "jobs": "Připojit k týmu",
"hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022.", "hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022. Přečtěte si více v <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ohlášení</a>.",
"hero-overlay-card-3-p-3": "Přečtěte si více v <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ohlášení</a>.",
"docs-dropdown-9": "Ke stažení", "docs-dropdown-9": "Ke stažení",
"docs-dropdown-10": "Transparentnost", "docs-dropdown-10": "Transparentnost",
"docs-dropdown-11": "FAQ (často kladené dotazy)", "docs-dropdown-11": "FAQ (často kladené dotazy)",
"docs-dropdown-12": "Bezpečnost" "docs-dropdown-12": "Bezpečnost"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"f-droid-page-simplex-chat-repo-section-text": "Um es Ihrem F-Droid-Client hinzuzufügen, <span class='hide-on-mobile'>scannen Sie den QR-Code oder</span> nutzen Sie diese URL:", "f-droid-page-simplex-chat-repo-section-text": "Um es Ihrem F-Droid-Client hinzuzufügen, <span class='hide-on-mobile'>scannen Sie den QR-Code oder</span> nutzen Sie diese URL:",
"comparison-section-list-point-4a": "SimpleX-Relais können die E2E-Verschlüsselung nicht kompromittieren. Überprüfen Sie den Sicherheitscode, um einen möglichen Angriff auf den Out-of-Band-Kanal zu entschärfen", "comparison-section-list-point-4a": "SimpleX-Relais können die E2E-Verschlüsselung nicht kompromittieren. Überprüfen Sie den Sicherheitscode, um einen möglichen Angriff auf den Out-of-Band-Kanal zu entschärfen",
"hero-overlay-3-title": "Sicherheits-Gutachten", "hero-overlay-3-title": "Sicherheits-Gutachten",
"hero-overlay-card-3-p-2": "Trail of Bits untersuchte im November 2022 die kryptografischen und Netzwerk-Komponenten der SimpleX-Plattform.", "hero-overlay-card-3-p-2": "Trail of Bits untersuchte im November 2022 die kryptografischen und Netzwerk-Komponenten der SimpleX-Plattform. Lesen Sie mehr dazu in der <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Ankündigung</a>.",
"hero-overlay-card-3-p-3": "Lesen Sie mehr dazu in der <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Ankündigung</a>.",
"jobs": "Treten Sie dem Team bei", "jobs": "Treten Sie dem Team bei",
"hero-overlay-3-textlink": "Sicherheits-Gutachten", "hero-overlay-3-textlink": "Sicherheits-Gutachten",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> ist eine führende Security- und Technologie-Unternehmensberatung, deren Kunden aus den Bereichen Big-Tech, Regierungsbehörden und großen Blockchain-Projekten stammen.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> ist eine führende Security- und Technologie-Unternehmensberatung, deren Kunden aus den Bereichen Big-Tech, Regierungsbehörden und großen Blockchain-Projekten stammen.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "Transparent", "docs-dropdown-10": "Transparent",
"docs-dropdown-11": "FAQ", "docs-dropdown-11": "FAQ",
"docs-dropdown-12": "Sicherheit" "docs-dropdown-12": "Sicherheit"
} }
+5 -5
View File
@@ -30,12 +30,12 @@
"hero-p-1": "Other apps have user IDs: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.<br> SimpleX does not, <strong>not even random numbers</strong>.<br> This radically improves your privacy.", "hero-p-1": "Other apps have user IDs: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.<br> SimpleX does not, <strong>not even random numbers</strong>.<br> This radically improves your privacy.",
"hero-overlay-1-textlink": "Why user IDs are bad for privacy?", "hero-overlay-1-textlink": "Why user IDs are bad for privacy?",
"hero-overlay-2-textlink": "How does SimpleX work?", "hero-overlay-2-textlink": "How does SimpleX work?",
"hero-overlay-3-textlink": "Security assessment", "hero-overlay-3-textlink": "Security assessments",
"hero-2-header": "Make a private connection", "hero-2-header": "Make a private connection",
"hero-2-header-desc": "The video shows how you connect to your friend via their 1-time QR-code, in person or via a video link. You can also connect by sharing an invitation link.", "hero-2-header-desc": "The video shows how you connect to your friend via their 1-time QR-code, in person or via a video link. You can also connect by sharing an invitation link.",
"hero-overlay-1-title": "How does SimpleX work?", "hero-overlay-1-title": "How does SimpleX work?",
"hero-overlay-2-title": "Why user IDs are bad for privacy?", "hero-overlay-2-title": "Why user IDs are bad for privacy?",
"hero-overlay-3-title": "Security assessment", "hero-overlay-3-title": "Security assessments",
"feature-1-title": "E2E-encrypted messages with markdown and editing", "feature-1-title": "E2E-encrypted messages with markdown and editing",
"feature-2-title": "E2E-encrypted<br>images, videos and files", "feature-2-title": "E2E-encrypted<br>images, videos and files",
"feature-3-title": "E2E-encrypted decentralized groups &mdash; only users know they exist", "feature-3-title": "E2E-encrypted decentralized groups &mdash; only users know they exist",
@@ -102,8 +102,8 @@
"hero-overlay-card-2-p-3": "Even with the most private apps that use Tor v3 services, if you talk to two different contacts via the same profile they can prove that they are connected to the same person.", "hero-overlay-card-2-p-3": "Even with the most private apps that use Tor v3 services, if you talk to two different contacts via the same profile they can prove that they are connected to the same person.",
"hero-overlay-card-2-p-4": "SimpleX protects against these attacks by not having any user IDs in its design. And, if you use Incognito mode, you will have a different display name for each contact, avoiding any shared data between them.", "hero-overlay-card-2-p-4": "SimpleX protects against these attacks by not having any user IDs in its design. And, if you use Incognito mode, you will have a different display name for each contact, avoiding any shared data between them.",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is a leading security and technology consultancy whose clients include big tech, governmental agencies and major blockchain projects.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is a leading security and technology consultancy whose clients include big tech, governmental agencies and major blockchain projects.",
"hero-overlay-card-3-p-2": "Trail of Bits reviewed SimpleX platform cryptography and networking components in November 2022.", "hero-overlay-card-3-p-2": "Trail of Bits reviewed SimpleX network cryptography and networking components in November 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Read more</a>.",
"hero-overlay-card-3-p-3": "Read more in <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">the announcement</a>.", "hero-overlay-card-3-p-3": "Trail of Bits reviewed cryptographic design of SimpleX network protocols in July 2024. <a href=\"/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html\">Read more</a>.",
"simplex-network-overlay-card-1-p-1": "<a href='https://en.wikipedia.org/wiki/Peer-to-peer'>P2P</a> messaging protocols and apps have various problems that make them less reliable than SimpleX, more complex to analyse, and vulnerable to several types of attack.", "simplex-network-overlay-card-1-p-1": "<a href='https://en.wikipedia.org/wiki/Peer-to-peer'>P2P</a> messaging protocols and apps have various problems that make them less reliable than SimpleX, more complex to analyse, and vulnerable to several types of attack.",
"simplex-network-overlay-card-1-li-1": "P2P networks rely on some variant of <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a> to route messages. DHT designs have to balance delivery guarantee and latency. SimpleX has both better delivery guarantee and lower latency than P2P, because the message can be redundantly passed via several servers in parallel, using the servers chosen by the recipient. In P2P networks the message is passed through <em>O(log N)</em> nodes sequentially, using nodes chosen by the algorithm.", "simplex-network-overlay-card-1-li-1": "P2P networks rely on some variant of <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a> to route messages. DHT designs have to balance delivery guarantee and latency. SimpleX has both better delivery guarantee and lower latency than P2P, because the message can be redundantly passed via several servers in parallel, using the servers chosen by the recipient. In P2P networks the message is passed through <em>O(log N)</em> nodes sequentially, using nodes chosen by the algorithm.",
"simplex-network-overlay-card-1-li-2": "SimpleX design, unlike most P2P networks, has no global user identifiers of any kind, even temporary, and only uses temporary pairwise identifiers, providing better anonymity and metadata protection.", "simplex-network-overlay-card-1-li-2": "SimpleX design, unlike most P2P networks, has no global user identifiers of any kind, even temporary, and only uses temporary pairwise identifiers, providing better anonymity and metadata protection.",
@@ -256,4 +256,4 @@
"jobs": "Join team", "jobs": "Join team",
"please-enable-javascript": "Please enable JavaScript to see the QR code.", "please-enable-javascript": "Please enable JavaScript to see the QR code.",
"please-use-link-in-mobile-app": "Please use the link in the mobile app" "please-use-link-in-mobile-app": "Please use the link in the mobile app"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen varios días más tarde en este repositorio", "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen varios días más tarde en este repositorio",
"comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo", "comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo",
"hero-overlay-3-title": "Evaluación de la seguridad", "hero-overlay-3-title": "Evaluación de la seguridad",
"hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022.", "hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Más información</a>.",
"hero-overlay-card-3-p-3": "Más información en <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">la noticia</a>.",
"jobs": "Únete al equipo", "jobs": "Únete al equipo",
"hero-overlay-3-textlink": "Evaluación de la seguridad", "hero-overlay-3-textlink": "Evaluación de la seguridad",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "Transparencia", "docs-dropdown-10": "Transparencia",
"docs-dropdown-11": "FAQ", "docs-dropdown-11": "FAQ",
"docs-dropdown-12": "Seguridad" "docs-dropdown-12": "Seguridad"
} }
+2 -3
View File
@@ -249,8 +249,7 @@
"hero-overlay-3-title": "Turvallisuuden arviointi", "hero-overlay-3-title": "Turvallisuuden arviointi",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> on johtava turvallisuus- ja teknologiakonsultointiyritys, jonka asiakkaita ovat muun muassa suuret teknologiayritykset, valtion virastot ja suuret lohkoketjuprojektit.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> on johtava turvallisuus- ja teknologiakonsultointiyritys, jonka asiakkaita ovat muun muassa suuret teknologiayritykset, valtion virastot ja suuret lohkoketjuprojektit.",
"hero-overlay-3-textlink": "Turvallisuusarviointi", "hero-overlay-3-textlink": "Turvallisuusarviointi",
"hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022.", "hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022. Lue lisää <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ilmoituksesta</a>.",
"hero-overlay-card-3-p-3": "Lue lisää <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ilmoituksesta</a>.",
"please-enable-javascript": "Ota JavaScript käyttöön nähdäksesi QR-koodin.", "please-enable-javascript": "Ota JavaScript käyttöön nähdäksesi QR-koodin.",
"please-use-link-in-mobile-app": "Käytä mobiilisovelluksessa olevaa linkkiä" "please-use-link-in-mobile-app": "Käytä mobiilisovelluksessa olevaa linkkiä"
} }
+2 -3
View File
@@ -246,8 +246,7 @@
"stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org", "stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org",
"comparison-section-list-point-4a": "Les relais SimpleX ne peuvent pas compromettre le chiffrement e2e. Vérifier le code de sécurité pour limiter les attaques sur le canal hors bande", "comparison-section-list-point-4a": "Les relais SimpleX ne peuvent pas compromettre le chiffrement e2e. Vérifier le code de sécurité pour limiter les attaques sur le canal hors bande",
"hero-overlay-3-title": "Évaluation de sécurité", "hero-overlay-3-title": "Évaluation de sécurité",
"hero-overlay-card-3-p-2": "Trail of Bits a examiné les composants cryptographiques et réseau de la plateforme SimpleX en novembre 2022.", "hero-overlay-card-3-p-2": "Trail of Bits a examiné les composants cryptographiques et réseau de la plateforme SimpleX en novembre 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">En savoir plus</a>.",
"hero-overlay-card-3-p-3": "En savoir plus <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">sur l'annonce</a>.",
"jobs": "Rejoignez notre équipe", "jobs": "Rejoignez notre équipe",
"hero-overlay-3-textlink": "Évaluation de sécurité", "hero-overlay-3-textlink": "Évaluation de sécurité",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> est un cabinet leader dans le secteur de la sécurité et des technologies qui compte parmi ses clients des grandes entreprises de la tech, des agences gouvernementales et d'importants projets de blockchain.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> est un cabinet leader dans le secteur de la sécurité et des technologies qui compte parmi ses clients des grandes entreprises de la tech, des agences gouvernementales et d'importants projets de blockchain.",
@@ -257,4 +256,4 @@
"docs-dropdown-10": "Transparence", "docs-dropdown-10": "Transparence",
"docs-dropdown-12": "Sécurité", "docs-dropdown-12": "Sécurité",
"docs-dropdown-11": "FAQ" "docs-dropdown-11": "FAQ"
} }
+2 -3
View File
@@ -87,12 +87,11 @@
"simplex-unique-1-title": "יש לך פרטיות מלאה", "simplex-unique-1-title": "יש לך פרטיות מלאה",
"simplex-private-card-10-point-1": "SimpleX משתמש בכתובות ואישורים אנונימיים זמניים בזוגות עבור כל איש קשר של משתמש או חבר בקבוצה.", "simplex-private-card-10-point-1": "SimpleX משתמש בכתובות ואישורים אנונימיים זמניים בזוגות עבור כל איש קשר של משתמש או חבר בקבוצה.",
"privacy-matters-overlay-card-1-p-1": "הרבה חברות גדולות משתמשות במידע עם מי אתה בקשר כדי להעריך את ההכנסה שלך, למכור לך מוצרים שאתה לא באמת צריך ולקבוע את המחירים.", "privacy-matters-overlay-card-1-p-1": "הרבה חברות גדולות משתמשות במידע עם מי אתה בקשר כדי להעריך את ההכנסה שלך, למכור לך מוצרים שאתה לא באמת צריך ולקבוע את המחירים.",
"hero-overlay-card-3-p-2": "Trail of Bits סקרה את רכיבי ההצפנה והרשת של פלטפורמת SimpleX בנובמבר 2022.", "hero-overlay-card-3-p-2": "Trail of Bits סקרה את רכיבי ההצפנה והרשת של פלטפורמת SimpleX בנובמבר 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">קרא עוד ב הודעה</a>.",
"hero-overlay-card-2-p-4": "SimpleX מגן מפני התקפות אלה בכך שאין בעיצובו מזהי משתמש. ואם אתה משתמש במצב זהות נסתרת, יהיה לך שם תצוגה שונה לכל איש קשר, תוך הימנעות מכל מידע משותף ביניהם.", "hero-overlay-card-2-p-4": "SimpleX מגן מפני התקפות אלה בכך שאין בעיצובו מזהי משתמש. ואם אתה משתמש במצב זהות נסתרת, יהיה לך שם תצוגה שונה לכל איש קשר, תוך הימנעות מכל מידע משותף ביניהם.",
"hero-overlay-card-2-p-1": "כאשר למשתמשים יש זהויות מתמשכות, גם אם זה רק מספר אקראי, כמו מזהה הפעלה, קיים סיכון שהספק או התוקף יוכלו לראות כיצד המשתמשים מחוברים וכמה הודעות הם שולחים.", "hero-overlay-card-2-p-1": "כאשר למשתמשים יש זהויות מתמשכות, גם אם זה רק מספר אקראי, כמו מזהה הפעלה, קיים סיכון שהספק או התוקף יוכלו לראות כיצד המשתמשים מחוברים וכמה הודעות הם שולחים.",
"hero-overlay-card-1-p-5": "רק מכשירי לקוח מאחסנים פרופילי משתמשים, אנשי קשר וקבוצות; ההודעות נשלחות עם הצפנה דו-שכבתית מקצה לקצה.", "hero-overlay-card-1-p-5": "רק מכשירי לקוח מאחסנים פרופילי משתמשים, אנשי קשר וקבוצות; ההודעות נשלחות עם הצפנה דו-שכבתית מקצה לקצה.",
"privacy-matters-overlay-card-1-p-2": "קמעונאים מקוונים יודעים שאנשים עם הכנסה נמוכה יותר נוטים יותר לבצע רכישות דחופות, ולכן הם עשויים לגבות מחירים גבוהים יותר או להסיר הנחות.", "privacy-matters-overlay-card-1-p-2": "קמעונאים מקוונים יודעים שאנשים עם הכנסה נמוכה יותר נוטים יותר לבצע רכישות דחופות, ולכן הם עשויים לגבות מחירים גבוהים יותר או להסיר הנחות.",
"hero-overlay-card-3-p-3": "קרא עוד ב<a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">הודעה</a>.",
"hero-overlay-card-1-p-2": "כדי להעביר הודעות, במקום מזהי משתמש המשמשים את כל הפלטפורמות האחרות, SimpleX משתמש במזהים אנונימיים זמניים זוגיים של תורי הודעות, נפרדים עבור כל אחד מהחיבורים שלך &mdash; אין מזהים לטווח ארוך.", "hero-overlay-card-1-p-2": "כדי להעביר הודעות, במקום מזהי משתמש המשמשים את כל הפלטפורמות האחרות, SimpleX משתמש במזהים אנונימיים זמניים זוגיים של תורי הודעות, נפרדים עבור כל אחד מהחיבורים שלך &mdash; אין מזהים לטווח ארוך.",
"hero-overlay-card-2-p-2": "לאחר מכן הם יוכלו לקשר מידע זה עם הרשתות החברתיות הציבוריות הקיימות, ולקבוע כמה זהויות אמיתיות.", "hero-overlay-card-2-p-2": "לאחר מכן הם יוכלו לקשר מידע זה עם הרשתות החברתיות הציבוריות הקיימות, ולקבוע כמה זהויות אמיתיות.",
"privacy-matters-overlay-card-1-p-3": "חלק מחברות פיננסיות וביטוח משתמשות בגרפים חברתיים כדי לקבוע שיעורי ריבית ופרמיות. לעתים קרובות זה גורם לאנשים עם הכנסה נמוכה יותר לשלם יותר &mdash; זה ידוע בתור <a href='https://fairbydesign.com/povertypremium/' target='_blank'>'פרמיית עוני'</a>.", "privacy-matters-overlay-card-1-p-3": "חלק מחברות פיננסיות וביטוח משתמשות בגרפים חברתיים כדי לקבוע שיעורי ריבית ופרמיות. לעתים קרובות זה גורם לאנשים עם הכנסה נמוכה יותר לשלם יותר &mdash; זה ידוע בתור <a href='https://fairbydesign.com/povertypremium/' target='_blank'>'פרמיית עוני'</a>.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "שקיפות", "docs-dropdown-10": "שקיפות",
"docs-dropdown-11": "שאלות ותשובות", "docs-dropdown-11": "שאלות ותשובות",
"docs-dropdown-12": "אבטחה" "docs-dropdown-12": "אבטחה"
} }
+2 -3
View File
@@ -95,7 +95,7 @@
"hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.",
"hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.",
"hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit.", "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">További információk</a>.",
"simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a> valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet <em>O(log N)</em> csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a> valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet <em>O(log N)</em> csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.",
"simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.",
"simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a <a href='https://en.wikipedia.org/wiki/Man-in-the-middle_attack'>MITM-támadás</a> problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a <a href='https://en.wikipedia.org/wiki/Man-in-the-middle_attack'>MITM-támadás</a> problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.",
@@ -229,7 +229,6 @@
"simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.",
"privacy-matters-1-title": "Hirdetés és árdiszkrimináció", "privacy-matters-1-title": "Hirdetés és árdiszkrimináció",
"hero-overlay-card-1-p-3": "Ön határozza meg, hogy melyik kiszolgáló(ka)t használja az üzenetek fogadására, a kapcsolatokhoz &mdash; azokat a kiszolgálókat, amelyeket az üzenetek küldésére használ. Minden beszélgetés két különböző kiszolgálót használ.", "hero-overlay-card-1-p-3": "Ön határozza meg, hogy melyik kiszolgáló(ka)t használja az üzenetek fogadására, a kapcsolatokhoz &mdash; azokat a kiszolgálókat, amelyeket az üzenetek küldésére használ. Minden beszélgetés két különböző kiszolgálót használ.",
"hero-overlay-card-3-p-3": "További információk <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">a közleményben</a>.",
"simplex-network-overlay-card-1-p-1": "A <a href='https://en.wikipedia.org/wiki/Peer-to-peer'>P2P</a> üzenetküldő protokollok és alkalmazások számos problémával küzdenek, amelyek miatt kevésbé megbízhatóak, mint a SimpleX, bonyolultabb az elemzésük és többféle támadással szemben sebezhetőek.", "simplex-network-overlay-card-1-p-1": "A <a href='https://en.wikipedia.org/wiki/Peer-to-peer'>P2P</a> üzenetküldő protokollok és alkalmazások számos problémával küzdenek, amelyek miatt kevésbé megbízhatóak, mint a SimpleX, bonyolultabb az elemzésük és többféle támadással szemben sebezhetőek.",
"chat-bot-example": "Chat bot példa", "chat-bot-example": "Chat bot példa",
"simplex-private-3-title": "Biztonságos, hitelesített<br>TLS adatátvitel", "simplex-private-3-title": "Biztonságos, hitelesített<br>TLS adatátvitel",
@@ -256,4 +255,4 @@
"simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül",
"simplex-chat-repo": "SimpleX Chat tároló", "simplex-chat-repo": "SimpleX Chat tároló",
"stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid <span class='hide-on-mobile'>scansiona il codice QR o</span> usa questo URL:", "f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid <span class='hide-on-mobile'>scansiona il codice QR o</span> usa questo URL:",
"comparison-section-list-point-4a": "I relay di SimpleX non possono compromettere la crittografia e2e. Verifica il codice di sicurezza per mitigare gli attacchi sul canale fuori banda", "comparison-section-list-point-4a": "I relay di SimpleX non possono compromettere la crittografia e2e. Verifica il codice di sicurezza per mitigare gli attacchi sul canale fuori banda",
"hero-overlay-3-title": "Valutazione della sicurezza", "hero-overlay-3-title": "Valutazione della sicurezza",
"hero-overlay-card-3-p-2": "Trail of Bits ha revisionato i componenti di crittografia e di rete della piattaforma SimpleX nel novembre 2022.", "hero-overlay-card-3-p-2": "Trail of Bits ha revisionato i componenti di crittografia e di rete della piattaforma SimpleX nel novembre 2022. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Maggiori informazioni</a>.",
"hero-overlay-card-3-p-3": "Maggiori informazioni nell'<a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">annuncio</a>.",
"jobs": "Unisciti al team", "jobs": "Unisciti al team",
"hero-overlay-3-textlink": "Valutazione della sicurezza", "hero-overlay-3-textlink": "Valutazione della sicurezza",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> è leader nella consulenza di sicurezza e tecnologia, i cui clienti includono grandi aziende, agenzie governative e importanti progetti di blockchain.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> è leader nella consulenza di sicurezza e tecnologia, i cui clienti includono grandi aziende, agenzie governative e importanti progetti di blockchain.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "Trasparenza", "docs-dropdown-10": "Trasparenza",
"docs-dropdown-12": "Sicurezza", "docs-dropdown-12": "Sicurezza",
"docs-dropdown-11": "Domande frequenti" "docs-dropdown-11": "Domande frequenti"
} }
+2 -3
View File
@@ -245,15 +245,14 @@
"f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat と F-Droid.org リポジトリは、異なるキーを使用してビルドに署名します。 切り替えるには、チャット データベースを<a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>エクスポート</a>し、アプリを再インストールしてください。", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat と F-Droid.org リポジトリは、異なるキーを使用してビルドに署名します。 切り替えるには、チャット データベースを<a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>エクスポート</a>し、アプリを再インストールしてください。",
"simplex-private-5-title": "何レイヤーもの<br>コンテンツパディング", "simplex-private-5-title": "何レイヤーもの<br>コンテンツパディング",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a>は、大手ハイテク企業、政府機関、主要なブロックチェーン・プロジェクトなどを顧客に持つ、セキュリティとテクノロジーの大手コンサルタント会社です。", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a>は、大手ハイテク企業、政府機関、主要なブロックチェーン・プロジェクトなどを顧客に持つ、セキュリティとテクノロジーの大手コンサルタント会社です。",
"hero-overlay-card-3-p-3": "詳しくは <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">お知らせ</a>をご覧ください。",
"jobs": "チームに参加する", "jobs": "チームに参加する",
"hero-overlay-3-textlink": "セキュリティ監査", "hero-overlay-3-textlink": "セキュリティ監査",
"hero-overlay-3-title": "セキュリティ監査", "hero-overlay-3-title": "セキュリティ監査",
"hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。", "hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。<a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">詳しくは お知らせをご覧ください。</a>",
"docs-dropdown-9": "ダウンロード", "docs-dropdown-9": "ダウンロード",
"please-enable-javascript": "QRコードを表示するためにJavaScriptを有効にしてください。", "please-enable-javascript": "QRコードを表示するためにJavaScriptを有効にしてください。",
"please-use-link-in-mobile-app": "このリンクをモバイルアプリで使用してください", "please-use-link-in-mobile-app": "このリンクをモバイルアプリで使用してください",
"docs-dropdown-10": "透明性", "docs-dropdown-10": "透明性",
"docs-dropdown-11": "よくある質問", "docs-dropdown-11": "よくある質問",
"docs-dropdown-12": "セキュリティ" "docs-dropdown-12": "セキュリティ"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"docs-dropdown-8": "SimpleX Directory Service", "docs-dropdown-8": "SimpleX Directory Service",
"comparison-section-list-point-4a": "SimpleX relais kunnen de e2e-versleuteling niet in gevaar brengen. Controleer de beveiligingscode om aanvallen op out-of-band kanalen te beperken", "comparison-section-list-point-4a": "SimpleX relais kunnen de e2e-versleuteling niet in gevaar brengen. Controleer de beveiligingscode om aanvallen op out-of-band kanalen te beperken",
"hero-overlay-3-title": "Beveiligings beoordeling", "hero-overlay-3-title": "Beveiligings beoordeling",
"hero-overlay-card-3-p-2": "Trail of Bits heeft in november 2022 de cryptografie en netwerkcomponenten van het SimpleX-platform beoordeeld.", "hero-overlay-card-3-p-2": "Trail of Bits heeft in november 2022 de cryptografie en netwerkcomponenten van het SimpleX-platform beoordeeld. Lees meer in <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">de aankondiging</a>.",
"hero-overlay-card-3-p-3": "Lees meer in <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">de aankondiging</a>.",
"jobs": "Sluit je aan bij het team", "jobs": "Sluit je aan bij het team",
"hero-overlay-3-textlink": "Beveiligings beoordeling", "hero-overlay-3-textlink": "Beveiligings beoordeling",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is een toonaangevend beveiligings- en technologieadviesbureau met klanten onder meer grote technologiebedrijven, overheidsinstanties en grote blockchain-projecten.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is een toonaangevend beveiligings- en technologieadviesbureau met klanten onder meer grote technologiebedrijven, overheidsinstanties en grote blockchain-projecten.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "Transparantie", "docs-dropdown-10": "Transparantie",
"docs-dropdown-11": "FAQ", "docs-dropdown-11": "FAQ",
"docs-dropdown-12": "Beveiliging" "docs-dropdown-12": "Beveiliging"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są kilka dni później", "releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są kilka dni później",
"comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy", "comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy",
"hero-overlay-3-title": "Ocena bezpieczeństwa", "hero-overlay-3-title": "Ocena bezpieczeństwa",
"hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022.", "hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022. Przeczytaj więcej w <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ogłoszeniach</a>.",
"hero-overlay-card-3-p-3": "Przeczytaj więcej w <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ogłoszeniach</a>.",
"jobs": "Dołącz do zespołu", "jobs": "Dołącz do zespołu",
"hero-overlay-3-textlink": "Ocena bezpieczeństwa", "hero-overlay-3-textlink": "Ocena bezpieczeństwa",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> jest wiodącą firmą konsultingową w zakresie bezpieczeństwa i technologii, której klientami są duże firmy technologiczne, agencje rządowe i główne projekty blockchain.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> jest wiodącą firmą konsultingową w zakresie bezpieczeństwa i technologii, której klientami są duże firmy technologiczne, agencje rządowe i główne projekty blockchain.",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "Przezroczystość", "docs-dropdown-10": "Przezroczystość",
"docs-dropdown-12": "Bezpieczeństwo", "docs-dropdown-12": "Bezpieczeństwo",
"docs-dropdown-11": "Często zadawane pytania" "docs-dropdown-11": "Często zadawane pytania"
} }
+2 -3
View File
@@ -246,8 +246,7 @@
"hero-overlay-3-textlink": "Avaliação Segura", "hero-overlay-3-textlink": "Avaliação Segura",
"hero-overlay-3-title": "Avaliação Segura", "hero-overlay-3-title": "Avaliação Segura",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> é uma consultoria líder em segurança e tecnologia cujos clientes incluem grandes empresas de tecnologia, agências governamentais e grandes projetos de blockchain.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> é uma consultoria líder em segurança e tecnologia cujos clientes incluem grandes empresas de tecnologia, agências governamentais e grandes projetos de blockchain.",
"hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022.", "hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022. Leia mais em <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">o anúncio</a>.",
"hero-overlay-card-3-p-3": "Leia mais em <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">o anúncio</a>.",
"f-droid-page-f-droid-org-repo-section-text": "Os repositórios SimpleX Chat e F-Droid.org assinam compilações com chaves diferentes. Para mudar, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>exporte</a> o banco de dados de bate-papo e reinstale o aplicativo.", "f-droid-page-f-droid-org-repo-section-text": "Os repositórios SimpleX Chat e F-Droid.org assinam compilações com chaves diferentes. Para mudar, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>exporte</a> o banco de dados de bate-papo e reinstale o aplicativo.",
"please-enable-javascript": "Por favor habilite o JavaScript para ver o QR code.", "please-enable-javascript": "Por favor habilite o JavaScript para ver o QR code.",
"jobs": "Junte-se à equipe", "jobs": "Junte-se à equipe",
@@ -256,4 +255,4 @@
"docs-dropdown-11": "FAQ", "docs-dropdown-11": "FAQ",
"docs-dropdown-10": "Transparência", "docs-dropdown-10": "Transparência",
"docs-dropdown-12": "Segurança" "docs-dropdown-12": "Segurança"
} }
+3 -3
View File
@@ -37,7 +37,8 @@
"simplex-explained-tab-1-text": "1. Как это видят пользователи", "simplex-explained-tab-1-text": "1. Как это видят пользователи",
"tap-to-close": "Нажмите, чтобы закрыть", "tap-to-close": "Нажмите, чтобы закрыть",
"simplex-unique-card-4-p-2": "Вы можете <strong>использовать SimpleX со своими собственными серверами</strong> или с серверами, предоставленными нами &mdash; и при этом подключаться к любому пользователю SimpleX.", "simplex-unique-card-4-p-2": "Вы можете <strong>использовать SimpleX со своими собственными серверами</strong> или с серверами, предоставленными нами &mdash; и при этом подключаться к любому пользователю SimpleX.",
"hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX.", "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX. <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">Дополнительная информация</a>.",
"hero-overlay-card-3-p-3": "В июле 2024 года Trail of Bits провела обзор криптографического дизайна протоколов SimpleX. <a href=\"/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html\">Дополнительная информация</a>.",
"feature-1-title": "Сообщения зашифрованные E2E-шифрованием<br>с поддержкой markdown и редактированием", "feature-1-title": "Сообщения зашифрованные E2E-шифрованием<br>с поддержкой markdown и редактированием",
"comparison-point-4-text": "Единая или Централизованная сеть", "comparison-point-4-text": "Единая или Централизованная сеть",
"guide-dropdown-9": "Установление соединений", "guide-dropdown-9": "Установление соединений",
@@ -77,7 +78,6 @@
"simplex-unique-2-overlay-1-title": "Лучшая защита от спама и злоупотреблений", "simplex-unique-2-overlay-1-title": "Лучшая защита от спама и злоупотреблений",
"simplex-private-6-title": "Внеполосный<br>Обмен ключами", "simplex-private-6-title": "Внеполосный<br>Обмен ключами",
"join-us-on-GitHub": "Присоединяйтесь к нам на GitHub", "join-us-on-GitHub": "Присоединяйтесь к нам на GitHub",
"hero-overlay-card-3-p-3": "Подробнее читайте в <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">анонсе</a>.",
"comparison-section-header": "Сравнение с другими протоколами", "comparison-section-header": "Сравнение с другими протоколами",
"invitation-hero-header": "Вы получили одноразовую ссылку для подключения в SimpleX Chat", "invitation-hero-header": "Вы получили одноразовую ссылку для подключения в SimpleX Chat",
"no-secure": "Нет - безопасно", "no-secure": "Нет - безопасно",
@@ -256,4 +256,4 @@
"docs-dropdown-10": "Прозрачность", "docs-dropdown-10": "Прозрачность",
"docs-dropdown-12": "Безопасность", "docs-dropdown-12": "Безопасность",
"docs-dropdown-11": "Часто задаваемые вопросы" "docs-dropdown-11": "Часто задаваемые вопросы"
} }
+2 -3
View File
@@ -244,8 +244,7 @@
"stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, побудовані розробниками", "stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, побудовані розробниками",
"f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat та репозитарії F-Droid.org підписують збірки різними ключами. Щоб переключитися, будь ласка, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>експортуйте</a> базу даних чату та перевстановіть додаток.", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat та репозитарії F-Droid.org підписують збірки різними ключами. Щоб переключитися, будь ласка, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>експортуйте</a> базу даних чату та перевстановіть додаток.",
"hero-overlay-3-title": "Оцінка безпеки", "hero-overlay-3-title": "Оцінка безпеки",
"hero-overlay-card-3-p-2": "Trail of Bits переглянувало криптографію та компоненти мережі платформи SimpleX у листопаді 2022 року.", "hero-overlay-card-3-p-2": "Trail of Bits переглянувало криптографію та компоненти мережі платформи SimpleX у листопаді 2022 року. Читайте більше в <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">оголошенні</a>.",
"hero-overlay-card-3-p-3": "Читайте більше в <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">оголошенні</a>.",
"jobs": "Приєднатися до команди", "jobs": "Приєднатися до команди",
"hero-overlay-3-textlink": "Оцінка безпеки", "hero-overlay-3-textlink": "Оцінка безпеки",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> є провідною консалтинговою фірмою з безпеки та технологій, клієнтами якої є великі технологічні компанії, урядові агенції та великі проекти у сфері блокчейну.", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> є провідною консалтинговою фірмою з безпеки та технологій, клієнтами якої є великі технологічні компанії, урядові агенції та великі проекти у сфері блокчейну.",
@@ -256,4 +255,4 @@
"docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ",
"docs-dropdown-10": "Прозорість", "docs-dropdown-10": "Прозорість",
"docs-dropdown-12": "Безпека" "docs-dropdown-12": "Безпека"
} }
+2 -3
View File
@@ -245,8 +245,7 @@
"docs-dropdown-8": "SimpleX 目录服务", "docs-dropdown-8": "SimpleX 目录服务",
"f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat 和 F-Droid.org 存储库使用不同的密钥对构建进行签名。 如需切换,请<a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>导出</a>聊天数据库并重新安装应用。", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat 和 F-Droid.org 存储库使用不同的密钥对构建进行签名。 如需切换,请<a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>导出</a>聊天数据库并重新安装应用。",
"hero-overlay-3-title": "安全性评估", "hero-overlay-3-title": "安全性评估",
"hero-overlay-card-3-p-2": "2022年11月份,Trail of Bits 审核了 SimpleX 平台的密码学和网络部件。", "hero-overlay-card-3-p-2": "2022年11月份,Trail of Bits 审核了 SimpleX 平台的密码学和网络部件。<a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">更多内容见 该公告</a>。",
"hero-overlay-card-3-p-3": "更多内容见 <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">该公告</a>。",
"jobs": "加入团队", "jobs": "加入团队",
"hero-overlay-3-textlink": "安全性评估", "hero-overlay-3-textlink": "安全性评估",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> 是一家领先的安全和技术咨询企业,其客户包括大型科技公司、政府机构和重要的区块链项目。", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> 是一家领先的安全和技术咨询企业,其客户包括大型科技公司、政府机构和重要的区块链项目。",
@@ -256,4 +255,4 @@
"docs-dropdown-10": "透明度", "docs-dropdown-10": "透明度",
"docs-dropdown-11": "常问问题", "docs-dropdown-11": "常问问题",
"docs-dropdown-12": "安全性" "docs-dropdown-12": "安全性"
} }
@@ -0,0 +1,12 @@
<p><strong>New security audit!</strong></p>
<p class="mb-[12px]">Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps.
</p>
<p><strong>v6.1 is released:</strong></p>
<ul>
<li>Better calls: switch audio and video during the call.</li>
<li>Better iOS notifications: improved delivery, reduced traffic usage.</li>
<li>Better user experience: switch chat profiles, customizable message shapes, forward up to 20 messages.</li>
</ul>
@@ -0,0 +1,4 @@
<p>The Wired article 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 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.</p>
@@ -6,4 +6,4 @@
</p> </p>
<p> <p>
{{ "hero-overlay-card-3-p-3" | i18n({}, lang ) | safe }} {{ "hero-overlay-card-3-p-3" | i18n({}, lang ) | safe }}
</p> </p>