mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ace687b7d3 | |||
| 59b5d07252 | |||
| 82400aac66 | |||
| 7df7434a92 | |||
| f243f01bd1 | |||
| 6607c24de3 | |||
| 4086238090 | |||
| a1bbecc7ad | |||
| 78510b6fd3 | |||
| efa0d6d551 | |||
| 17214a55e9 | |||
| edf99fcd1d | |||
| 2ffabd1ef8 | |||
| 28383edb83 | |||
| 9175897acf | |||
| f3cd167502 | |||
| 7cde2cf6c2 | |||
| 3913043705 | |||
| a160acef12 | |||
| c54fae0136 | |||
| d57abfcc93 | |||
| 515a0ddfdd | |||
| b5d8c65249 | |||
| de94892fe7 | |||
| b7131e16f2 | |||
| 11a44dc1fd | |||
| 0af718f03f | |||
| f8f5c3c6be |
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+11
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+179
-5
@@ -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 — a protocol for sending files, — and XRCP — 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 — 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 — 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 — 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 — 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 — 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) — 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 — 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 — 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
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/article.html
|
||||||
|
title: "Wired’s 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"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wired’s 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 — 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 — that’s the point. To paint this as problematic solely because of who may use such apps misses the broader, critical context.
|
||||||
|
|
||||||
|
SimpleX’s 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 — the information that can reveal who you’re 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 — 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 doesn’t justify broad criticism. Additionally, the lobbying for client-side scanning, which Wired’s 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.
|
||||||
|
|
||||||
|
It’s 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 EU’s 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 — 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
@@ -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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -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).
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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,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
@@ -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
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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": "الأمان"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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 — only users know they exist",
|
"feature-3-title": "E2E-encrypted decentralized groups — 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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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ä"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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 משתמש במזהים אנונימיים זמניים זוגיים של תורי הודעות, נפרדים עבור כל אחד מהחיבורים שלך — אין מזהים לטווח ארוך.",
|
"hero-overlay-card-1-p-2": "כדי להעביר הודעות, במקום מזהי משתמש המשמשים את כל הפלטפורמות האחרות, SimpleX משתמש במזהים אנונימיים זמניים זוגיים של תורי הודעות, נפרדים עבור כל אחד מהחיבורים שלך — אין מזהים לטווח ארוך.",
|
||||||
"hero-overlay-card-2-p-2": "לאחר מכן הם יוכלו לקשר מידע זה עם הרשתות החברתיות הציבוריות הקיימות, ולקבוע כמה זהויות אמיתיות.",
|
"hero-overlay-card-2-p-2": "לאחר מכן הם יוכלו לקשר מידע זה עם הרשתות החברתיות הציבוריות הקיימות, ולקבוע כמה זהויות אמיתיות.",
|
||||||
"privacy-matters-overlay-card-1-p-3": "חלק מחברות פיננסיות וביטוח משתמשות בגרפים חברתיים כדי לקבוע שיעורי ריבית ופרמיות. לעתים קרובות זה גורם לאנשים עם הכנסה נמוכה יותר לשלם יותר — זה ידוע בתור <a href='https://fairbydesign.com/povertypremium/' target='_blank'>'פרמיית עוני'</a>.",
|
"privacy-matters-overlay-card-1-p-3": "חלק מחברות פיננסיות וביטוח משתמשות בגרפים חברתיים כדי לקבוע שיעורי ריבית ופרמיות. לעתים קרובות זה גורם לאנשים עם הכנסה נמוכה יותר לשלם יותר — זה ידוע בתור <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": "אבטחה"
|
||||||
}
|
}
|
||||||
@@ -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 — 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 — 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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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": "セキュリティ"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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> или с серверами, предоставленными нами — и при этом подключаться к любому пользователю SimpleX.",
|
"simplex-unique-card-4-p-2": "Вы можете <strong>использовать SimpleX со своими собственными серверами</strong> или с серверами, предоставленными нами — и при этом подключаться к любому пользователю 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": "Часто задаваемые вопросы"
|
||||||
}
|
}
|
||||||
@@ -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": "Безпека"
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user