From 6eb09625ab311ebb2513d9e7f98263f59a98ba91 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Mon, 23 Oct 2023 20:53:12 +0100
Subject: [PATCH 1/8] website: update copy
---
website/langs/en.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/website/langs/en.json b/website/langs/en.json
index 434aed834..1bb64c7ef 100644
--- a/website/langs/en.json
+++ b/website/langs/en.json
@@ -37,12 +37,12 @@
"hero-overlay-2-title": "Why user IDs are bad for privacy?",
"hero-overlay-3-title": "Security assessment",
"feature-1-title": "E2E-encrypted messages with markdown and editing",
- "feature-2-title": "E2E-encrypted
images and files",
- "feature-3-title": "Decentralized secret groups —
only users know they exist",
+ "feature-2-title": "E2E-encrypted
images, videos and files",
+ "feature-3-title": "E2E-encrypted decentralized groups — only users know they exist",
"feature-4-title": "E2E-encrypted voice messages",
"feature-5-title": "Disappearing messages",
"feature-6-title": "E2E-encrypted
audio and video calls",
- "feature-7-title": "Portable encrypted database — move your profile to another device",
+ "feature-7-title": "Portable encrypted app storage — move profile to another device",
"feature-8-title": "Incognito mode —
unique to SimpleX Chat",
"simplex-network-overlay-1-title": "Comparison with P2P messaging protocols",
"simplex-private-1-title": "2-layers of
end-to-end encryption",
From 66d8bb94d6d18df4e42690951ff4c41c2e13128e Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Mon, 23 Oct 2023 21:16:36 +0100
Subject: [PATCH 2/8] website: downloads page
---
docs/DOWNLOADS.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md
index 64b76e7fe..94b8d9197 100644
--- a/docs/DOWNLOADS.md
+++ b/docs/DOWNLOADS.md
@@ -7,7 +7,7 @@ revision: 01.10.2023
| Updated 01.10.2023 | Languages: EN |
# Download SimpleX apps
-The latest stable version is v5.3.1.
+The latest stable version is v5.3.2.
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
@@ -21,9 +21,9 @@ You can get the latest beta releases from [GitHub](https://github.com/simplex-ch
Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps.
-**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-ubuntu-22_04-x86_64.deb).
+**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-ubuntu-22_04-x86_64.deb).
-**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
+**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.0/simplex-desktop-windows-x86-64.msi) (BETA).
@@ -31,14 +31,14 @@ Using the same profile as on mobile device is not yet supported – you need to
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu).
-**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-armv7a.apk).
+**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-armv7a.apk).
## Terminal (console) app
See [Using terminal app](/docs/CLI.md).
-**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-chat-ubuntu-22_04-x86-64).
+**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-chat-ubuntu-22_04-x86-64).
-**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
+**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
-**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-chat-windows-x86-64).
+**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-chat-windows-x86-64).
From ed1eef7362a1f21647d20628d97eab50fe928220 Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Tue, 24 Oct 2023 17:38:16 +0400
Subject: [PATCH 3/8] core: update simplexmq (inv locks) (#3274)
---
cabal.project | 2 +-
scripts/nix/sha256map.nix | 2 +-
stack.yaml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/cabal.project b/cabal.project
index 8a05c0bb9..eb557e52f 100644
--- a/cabal.project
+++ b/cabal.project
@@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
- tag: cf8b9c12ff5cbdc77d3b8866af2c761a546ec8fc
+ tag: 55a6157880396be899c010f880a42322cf65258a
source-repository-package
type: git
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index 60b9505fe..6c41ff2a1 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."cf8b9c12ff5cbdc77d3b8866af2c761a546ec8fc" = "0xcbvxz2nszm1sdh6gvmfzjf9n2ldsarmmzbl6j6b5hg9i1mppc6";
+ "https://github.com/simplex-chat/simplexmq.git"."55a6157880396be899c010f880a42322cf65258a" = "1fhhyi2060pp72izrqki6gazb759hcv9wypxf39jkwpqpvrn81hv";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp";
diff --git a/stack.yaml b/stack.yaml
index 99b9d179c..e6f6f49b7 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
- commit: cf8b9c12ff5cbdc77d3b8866af2c761a546ec8fc
+ commit: 55a6157880396be899c010f880a42322cf65258a
- github: kazu-yamamoto/http2
commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517
# - ../direct-sqlcipher
From f8332bac7f624d8003b9613ef425f0cf82b2d10b Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Tue, 24 Oct 2023 18:13:19 +0400
Subject: [PATCH 4/8] core: take chat lock earlier when joining group (#3272)
---
src/Simplex/Chat.hs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index 3581b80c2..c4b34666d 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -1547,12 +1547,12 @@ processChatCommand = \case
Nothing -> throwChatError $ CEGroupCantResendInvitation gInfo cName
| otherwise -> throwChatError $ CEGroupDuplicateMember cName
APIJoinGroup groupId -> withUser $ \user@User {userId} -> do
- (invitation, ct) <- withStore $ \db -> do
- inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db user groupId
- (inv,) <$> getContactViaMember db user fromMember
- let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} = invitation
- Contact {activeConn = Connection {peerChatVRange}} = ct
withChatLock "joinGroup" . procCmd $ do
+ (invitation, ct) <- withStore $ \db -> do
+ inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db user groupId
+ (inv,) <$> getContactViaMember db user fromMember
+ let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} = invitation
+ Contact {activeConn = Connection {peerChatVRange}} = ct
subMode <- chatReadVar subscriptionMode
dm <- directMessage $ XGrpAcpt membership.memberId
agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest dm subMode
From 239765e482b837f8dabbd82e74735713a915ea7e Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Tue, 24 Oct 2023 20:59:06 +0400
Subject: [PATCH 5/8] core: create new group with incognito membership (#3277)
---
src/Simplex/Chat.hs | 14 +++++----
src/Simplex/Chat/Controller.hs | 4 +--
src/Simplex/Chat/Store/Direct.hs | 11 -------
src/Simplex/Chat/Store/Groups.hs | 7 +++--
src/Simplex/Chat/Store/Shared.hs | 11 +++++++
src/Simplex/Chat/View.hs | 25 +++++++++++-----
tests/ChatTests/Groups.hs | 51 ++++++++++++++++++++++++++++++++
tests/ChatTests/Profiles.hs | 2 +-
8 files changed, 95 insertions(+), 30 deletions(-)
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index c4b34666d..c70c842c0 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -1510,13 +1510,15 @@ processChatCommand = \case
chatRef <- getChatRef user chatName
chatItemId <- getChatItemIdByText user chatRef msg
processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction
- APINewGroup userId gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
+ APINewGroup userId incognito gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
checkValidName displayName
gVar <- asks idsDrg
- groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile
+ -- [incognito] generate incognito profile for group membership
+ incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
+ groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile incognitoProfile
pure $ CRGroupCreated user groupInfo
- NewGroup gProfile -> withUser $ \User {userId} ->
- processChatCommand $ APINewGroup userId gProfile
+ NewGroup incognito gProfile -> withUser $ \User {userId} ->
+ processChatCommand $ APINewGroup userId incognito gProfile
APIAddMember groupId contactId memRole -> withUser $ \user -> withChatLock "addMember" $ do
-- TODO for large groups: no need to load all members to determine if contact is a member
(group, contact) <- withStore $ \db -> (,) <$> getGroup db user groupId <*> getContact db user contactId
@@ -5714,8 +5716,8 @@ chatCommandP =
("/help settings" <|> "/hs") $> ChatHelp HSSettings,
("/help db" <|> "/hd") $> ChatHelp HSDatabase,
("/help" <|> "/h") $> ChatHelp HSMain,
- ("/group " <|> "/g ") *> char_ '#' *> (NewGroup <$> groupProfile),
- "/_group " *> (APINewGroup <$> A.decimal <* A.space <*> jsonP),
+ ("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile),
+ "/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP),
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayName <* A.space <* char_ '@' <*> displayName <*> (memberRole <|> pure GRMember)),
("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayName),
("/member role " <|> "/mr ") *> char_ '#' *> (MemberRole <$> displayName <* A.space <* char_ '@' <*> displayName <*> memberRole),
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index d8851ad87..74501ad1e 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -363,8 +363,8 @@ data ChatCommand
| EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text}
| UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text}
| ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text}
- | APINewGroup UserId GroupProfile
- | NewGroup GroupProfile
+ | APINewGroup UserId IncognitoEnabled GroupProfile
+ | NewGroup IncognitoEnabled GroupProfile
| AddMember GroupName ContactName GroupMemberRole
| JoinGroup GroupName
| MemberRole GroupName ContactName GroupMemberRole
diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs
index 563cc337e..477361acd 100644
--- a/src/Simplex/Chat/Store/Direct.hs
+++ b/src/Simplex/Chat/Store/Direct.hs
@@ -193,17 +193,6 @@ createIncognitoProfile db User {userId} p = do
createdAt <- getCurrentTime
createIncognitoProfile_ db userId createdAt p
-createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
-createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
- DB.execute
- db
- [sql|
- INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?)
- |]
- (displayName, fullName, image, userId, Just True, createdAt, createdAt)
- insertedRowId db
-
createDirectContact :: DB.Connection -> User -> Connection -> Profile -> ExceptT StoreError IO Contact
createDirectContact db user@User {userId} activeConn@Connection {connId, localAlias} p@Profile {preferences} = do
createdAt <- liftIO getCurrentTime
diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs
index 76d68cc6b..0b296b17e 100644
--- a/src/Simplex/Chat/Store/Groups.hs
+++ b/src/Simplex/Chat/Store/Groups.hs
@@ -283,11 +283,12 @@ getGroupAndMember db User {userId, userContactId} groupMemberId =
in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection connRow})
-- | creates completely new group with a single member - the current user
-createNewGroup :: DB.Connection -> TVar ChaChaDRG -> User -> GroupProfile -> ExceptT StoreError IO GroupInfo
-createNewGroup db gVar user@User {userId} groupProfile = ExceptT $ do
+createNewGroup :: DB.Connection -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo
+createNewGroup db gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do
let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile
fullGroupPreferences = mergeGroupPreferences groupPreferences
currentTs <- getCurrentTime
+ customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile
withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do
groupId <- liftIO $ do
DB.execute
@@ -301,7 +302,7 @@ createNewGroup db gVar user@User {userId} groupProfile = ExceptT $ do
(ldn, userId, profileId, True, currentTs, currentTs, currentTs)
insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12
- membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs
+ membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}
diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs
index 2a90b54d7..2ad447aa8 100644
--- a/src/Simplex/Chat/Store/Shared.hs
+++ b/src/Simplex/Chat/Store/Shared.hs
@@ -184,6 +184,17 @@ createConnection_ db userId connType entityId acId peerChatVRange@(VersionRange
where
ent ct = if connType == ct then entityId else Nothing
+createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
+createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
+ DB.execute
+ db
+ [sql|
+ INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at)
+ VALUES (?,?,?,?,?,?,?)
+ |]
+ (displayName, fullName, image, userId, Just True, createdAt, createdAt)
+ insertedRowId db
+
setPeerChatVRange :: DB.Connection -> Int64 -> VersionRange -> IO ()
setPeerChatVRange db connId (VersionRange minVer maxVer) =
DB.execute
diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs
index 07cbec601..7f1e1f5c7 100644
--- a/src/Simplex/Chat/View.hs
+++ b/src/Simplex/Chat/View.hs
@@ -132,7 +132,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
CRUserContactLink u UserContactLink {connReqContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connReqContact <> autoAcceptStatus_ autoAccept
CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
- CRGroupCreated u g -> ttyUser u $ viewGroupCreated g
+ CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
CRGroupsList u gs -> ttyUser u $ viewGroupsList gs
CRSentGroupInvitation u g c _ ->
@@ -792,11 +792,22 @@ viewReceivedContactRequest c Profile {fullName} =
"to reject: " <> highlight ("/rc " <> viewName c) <> " (the sender will NOT be notified)"
]
-viewGroupCreated :: GroupInfo -> [StyledString]
-viewGroupCreated g =
- [ "group " <> ttyFullGroup g <> " is created",
- "to add members use " <> highlight ("/a " <> viewGroupName g <> " ") <> " or " <> highlight ("/create link #" <> viewGroupName g)
- ]
+viewGroupCreated :: GroupInfo -> Bool -> [StyledString]
+viewGroupCreated g testView =
+ case incognitoMembershipProfile g of
+ Just localProfile
+ | testView -> incognitoProfile' profile : message
+ | otherwise -> message
+ where
+ profile = fromLocalProfile localProfile
+ message =
+ [ "group " <> ttyFullGroup g <> " is created, your incognito profile for this group is " <> incognitoProfile' profile,
+ "to add members use " <> highlight ("/create link #" <> viewGroupName g)
+ ]
+ Nothing ->
+ [ "group " <> ttyFullGroup g <> " is created",
+ "to add members use " <> highlight ("/a " <> viewGroupName g <> " ") <> " or " <> highlight ("/create link #" <> viewGroupName g)
+ ]
viewCannotResendInvitation :: GroupInfo -> ContactName -> [StyledString]
viewCannotResendInvitation g c =
@@ -1672,7 +1683,7 @@ viewChatError logLevel = \case
_ -> ": you have insufficient permissions for this action, the required role is " <> plain (strEncode role)
CEGroupMemberInitialRole g role -> [ttyGroup' g <> ": initial role for group member cannot be " <> plain (strEncode role) <> ", use member or observer"]
CEContactIncognitoCantInvite -> ["you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito"]
- CEGroupIncognitoCantInvite -> ["you've connected to this group using an incognito profile - prohibited to invite contacts"]
+ CEGroupIncognitoCantInvite -> ["you are using an incognito profile for this group - prohibited to invite contacts"]
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> viewGroupName g)]
CEGroupMemberNotActive -> ["your group connection is not active yet, try later"]
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 7a8b1368b..97b749106 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -23,6 +23,7 @@ chatGroupTests = do
describe "chat groups" $ do
it "add contacts, create group and send/receive messages" testGroup
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
+ it "create group with incognito membership" testNewGroupIncognito
it "create and join group with 4 members" testGroup2
it "create and delete group" testGroupDelete
it "create group with the same displayName" testGroupSameName
@@ -277,6 +278,56 @@ testGroupShared alice bob cath checkMessages = do
alice #$> ("/_unread chat #1 on", id, "ok")
alice #$> ("/_unread chat #1 off", id, "ok")
+testNewGroupIncognito :: HasCallStack => FilePath -> IO ()
+testNewGroupIncognito =
+ testChat2 aliceProfile bobProfile $
+ \alice bob -> do
+ connectUsers alice bob
+
+ -- alice creates group with incognito membership
+ alice ##> "/g i team"
+ aliceIncognito <- getTermLine alice
+ alice <## ("group #team is created, your incognito profile for this group is " <> aliceIncognito)
+ alice <## "to add members use /create link #team"
+
+ -- alice invites bob
+ alice ##> "/a team bob"
+ alice <## "you are using an incognito profile for this group - prohibited to invite contacts"
+
+ alice ##> "/create link #team"
+ gLink <- getGroupLink alice "team" GRMember True
+ bob ##> ("/c " <> gLink)
+ bob <## "connection request sent!"
+ alice <## "bob_1 (Bob): accepting request to join group #team..."
+ _ <- getTermLine alice
+ concurrentlyN_
+ [ do
+ alice <## ("bob_1 (Bob): contact is connected, your incognito profile for this contact is " <> aliceIncognito)
+ alice <## "use /i bob_1 to print out this incognito profile again"
+ alice <## "bob_1 invited to group #team via your group link"
+ alice <## "#team: bob_1 joined the group",
+ do
+ bob <## (aliceIncognito <> ": contact is connected")
+ bob <## "#team: you joined the group"
+ ]
+
+ alice <##> bob
+
+ alice ?#> "@bob_1 hi, I'm incognito"
+ bob <# (aliceIncognito <> "> hi, I'm incognito")
+ bob #> ("@" <> aliceIncognito <> " hey, I'm bob")
+ alice ?<# "bob_1> hey, I'm bob"
+
+ alice ?#> "#team hello"
+ bob <# ("#team " <> aliceIncognito <> "> hello")
+ bob #> "#team hi there"
+ alice ?<# "#team bob_1> hi there"
+
+ alice ##> "/gs"
+ alice <## "i #team (2 members)"
+ bob ##> "/gs"
+ bob <## "#team (2 members)"
+
testGroup2 :: HasCallStack => FilePath -> IO ()
testGroup2 =
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs
index 68b925342..d806290d6 100644
--- a/tests/ChatTests/Profiles.hs
+++ b/tests/ChatTests/Profiles.hs
@@ -1085,7 +1085,7 @@ testJoinGroupIncognito =
]
-- cath cannot invite to the group because her membership is incognito
cath ##> "/a secret_club dan"
- cath <## "you've connected to this group using an incognito profile - prohibited to invite contacts"
+ cath <## "you are using an incognito profile for this group - prohibited to invite contacts"
-- alice invites dan
alice ##> "/a secret_club dan admin"
concurrentlyN_
From b58d61c3397f669bde0e0f34e482227ca425f0f3 Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Wed, 25 Oct 2023 04:27:58 +0800
Subject: [PATCH 6/8] android: delete files after sharing correctly (#3264)
---
.../simplex/common/views/chat/ComposeView.kt | 22 +++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
index 972bc6621..84a5879b2 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
@@ -324,8 +324,26 @@ fun ComposeView(
}
fun deleteUnusedFiles() {
- chatModel.filesToDelete.forEach { it.delete() }
- chatModel.filesToDelete.clear()
+ val shared = chatModel.sharedContent.value
+ if (shared == null) {
+ chatModel.filesToDelete.forEach { it.delete() }
+ chatModel.filesToDelete.clear()
+ } else {
+ val sharedPaths = when (shared) {
+ is SharedContent.Media -> shared.uris.map { it.toString() }
+ is SharedContent.File -> listOf(shared.uri.toString())
+ is SharedContent.Text -> emptyList()
+ }
+ // When sharing a file and pasting it in SimpleX itself, the file shouldn't be deleted before sending or before leaving the chat after sharing
+ chatModel.filesToDelete.removeAll { file ->
+ if (sharedPaths.any { it.endsWith(file.name) }) {
+ false
+ } else {
+ file.delete()
+ true
+ }
+ }
+ }
}
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
From 10f79aae6608a72b285f9cbdb121104aed60b48a Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Wed, 25 Oct 2023 04:39:43 +0800
Subject: [PATCH 7/8] android: alert on unsupported file path when sharing
(#3265)
* android: alert on unsupported file path when sharing
* update text
---------
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
---
.../src/main/java/chat/simplex/app/MainActivity.kt | 11 +++++++++++
.../src/commonMain/resources/MR/base/strings.xml | 2 ++
2 files changed, 13 insertions(+)
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
index 2dff3d604..19305c2e5 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
@@ -143,6 +143,7 @@ fun processExternalIntent(intent: Intent?) {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri
if (uri != null) {
+ if (uri.scheme != "content") return showNonContentUriAlert()
// Shared file that contains plain text, like `*.log` file
chatModel.sharedContent.value = SharedContent.File(text ?: "", uri.toURI())
} else if (text != null) {
@@ -153,12 +154,14 @@ fun processExternalIntent(intent: Intent?) {
isMediaIntent(intent) -> {
val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri
if (uri != null) {
+ if (uri.scheme != "content") return showNonContentUriAlert()
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri.toURI()))
} // All other mime types
}
else -> {
val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri
if (uri != null) {
+ if (uri.scheme != "content") return showNonContentUriAlert()
chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri.toURI())
}
}
@@ -173,6 +176,7 @@ fun processExternalIntent(intent: Intent?) {
isMediaIntent(intent) -> {
val uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) as? List
if (uris != null) {
+ if (uris.any { it.scheme != "content" }) return showNonContentUriAlert()
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris.map { it.toURI() })
} // All other mime types
}
@@ -185,6 +189,13 @@ fun processExternalIntent(intent: Intent?) {
fun isMediaIntent(intent: Intent): Boolean =
intent.type?.startsWith("image/") == true || intent.type?.startsWith("video/") == true
+private fun showNonContentUriAlert() {
+ AlertManager.shared.showAlertMsg(
+ title = generalGetString(MR.strings.non_content_uri_alert_title),
+ text = generalGetString(MR.strings.non_content_uri_alert_text)
+ )
+}
+
//fun testJson() {
// val str: String = """
// """.trimIndent()
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index 1ae85cd05..b5ffd6630 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -16,6 +16,8 @@
Opening database…
+ Invalid file path
+ You shared an invalid file path. Report the issue to the app developers.
connected
From 1dcd2760b0b803a9e050532c8505379db6516288 Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Wed, 25 Oct 2023 06:01:47 +0800
Subject: [PATCH 8/8] ui: show alert after saving profile with existing name
(#3273)
* android, desktop: show alert after saving profile with existing name
* ios: show alert after saving profile with existing name
* return statements
* better way of showing alert
---------
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
---
apps/ios/Shared/Model/SimpleXAPI.swift | 3 ++-
apps/ios/Shared/Views/Chat/ChatInfoView.swift | 4 ++--
apps/ios/Shared/Views/ChatList/ChatListNavLink.swift | 4 ++--
apps/ios/Shared/Views/UserSettings/UserProfile.swift | 4 +++-
apps/ios/SimpleXChat/AppGroup.swift | 6 +++---
.../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 3 +++
.../simplex/common/views/usersettings/UserProfileView.kt | 2 +-
7 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 99e8c0284..680a7132d 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -720,8 +720,9 @@ func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? {
let userId = try currentUserId("apiUpdateProfile")
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
switch r {
- case .userProfileNoChange: return nil
+ case .userProfileNoChange: return (profile, [])
case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts)
+ case .chatCmdError(_, .errorStore(.duplicateName)): return nil;
default: throw r
}
}
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index ec4cc0fc4..b90c9e747 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -242,7 +242,7 @@ struct ChatInfoView: View {
}
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.ready && contact.active {
- ActionSheet(
+ return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
@@ -251,7 +251,7 @@ struct ChatInfoView: View {
]
)
} else {
- ActionSheet(
+ return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { deleteContact() },
diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
index be912d666..088335d19 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
@@ -78,7 +78,7 @@ struct ChatListNavLink: View {
.frame(height: rowHeights[dynamicTypeSize])
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.ready && contact.active {
- ActionSheet(
+ return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
@@ -87,7 +87,7 @@ struct ChatListNavLink: View {
]
)
} else {
- ActionSheet(
+ return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift
index b1a362a5a..b64ec21de 100644
--- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift
@@ -174,11 +174,13 @@ struct UserProfile: View {
chatModel.updateCurrentUser(newProfile)
profile = newProfile
}
+ editProfile = false
+ } else {
+ alert = .duplicateUserError
}
} catch {
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
}
- editProfile = false
}
}
}
diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift
index 4943dbd4e..cc61fae53 100644
--- a/apps/ios/SimpleXChat/AppGroup.swift
+++ b/apps/ios/SimpleXChat/AppGroup.swift
@@ -83,9 +83,9 @@ public enum AppState: String {
public var canSuspend: Bool {
switch self {
- case .active: true
- case .bgRefresh: true
- default: false
+ case .active: return true
+ case .bgRefresh: return true
+ default: return false
}
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 58850ce05..1abc823c0 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -943,6 +943,9 @@ object ChatController {
val r = sendCmd(CC.ApiUpdateProfile(userId, profile))
if (r is CR.UserProfileNoChange) return profile to emptyList()
if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts
+ if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName) {
+ AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc))
+ }
Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}")
return null
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt
index 38c9e58ad..ea4ef79d4 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt
@@ -42,8 +42,8 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
val (newProfile, _) = updated
chatModel.updateCurrentUser(newProfile)
profile = newProfile
+ close()
}
- close()
}
}
)