From 6093219ce97093fd13f8a40622dcf75fc1484433 Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Thu, 25 May 2023 20:54:31 +0400
Subject: [PATCH] core: don't keep connection of the merged contact (#2507)
---
.../simplex/app/views/chat/ChatInfoView.kt | 13 ++++++-
.../app/src/main/res/values/strings.xml | 1 +
apps/ios/Shared/Views/Chat/ChatInfoView.swift | 8 +++++
src/Simplex/Chat.hs | 35 ++++++++++++++-----
src/Simplex/Chat/Store.hs | 25 ++++---------
src/Simplex/Chat/View.hs | 6 +---
tests/ChatTests/Groups.hs | 31 ++++++++--------
tests/ChatTests/Utils.hs | 7 +++-
8 files changed, 77 insertions(+), 49 deletions(-)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt
index a97d59138..c3c35aa7a 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt
@@ -196,8 +196,8 @@ fun ChatInfoLayout(
}
ContactPreferencesButton(openPreferences)
}
-
SectionDividerSpaced()
+
if (contact.contactLink != null) {
val context = LocalContext.current
SectionView(stringResource(R.string.address_section_title).uppercase()) {
@@ -208,6 +208,17 @@ fun ChatInfoLayout(
SectionDividerSpaced()
}
+ if (developerTools) {
+ SectionView(title = stringResource(R.string.contact_info_section_title_contact)) {
+ val connLevel = contact.activeConn.connLevel
+ val connLevelDesc =
+ if (connLevel == 0) stringResource(R.string.conn_level_desc_direct)
+ else String.format(generalGetString(R.string.conn_level_desc_indirect), connLevel)
+ InfoRow(stringResource(R.string.info_row_connection), connLevelDesc)
+ }
+ SectionDividerSpaced()
+ }
+
SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) {
SwitchAddressButton(switchContactAddress)
if (connStats != null) {
diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml
index 2017bae84..5f6094f3a 100644
--- a/apps/android/app/src/main/res/values/strings.xml
+++ b/apps/android/app/src/main/res/values/strings.xml
@@ -1140,6 +1140,7 @@
Member will be removed from group - this cannot be undone!
Remove
MEMBER
+ CONTACT
Role
Change role
Change
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index b72006b3f..2e083d86e 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -131,6 +131,14 @@ struct ChatInfoView: View {
}
}
+ if developerTools {
+ Section("Contact") {
+ let connLevel = contact.activeConn.connLevel
+ let connLevelDesc = connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), connLevel)
+ infoRow("Connection", connLevelDesc)
+ }
+ }
+
Section("Servers") {
networkStatusRow()
.onTapGesture {
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index 4b6b5d259..f344d24cb 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -2656,7 +2656,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
_ -> Nothing
processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> m ()
- processDirectMessage agentMsg connEntity conn@Connection {connId, viaUserContactLink, groupLinkId, customUserProfileId} = \case
+ processDirectMessage agentMsg connEntity conn@Connection {connId, viaUserContactLink, customUserProfileId} = \case
Nothing -> case agentMsg of
CONF confId _ connInfo -> do
-- [incognito] send saved profile
@@ -2758,7 +2758,6 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
whenUserNtfs user $ do
setActive $ ActiveC c
showToast (c <> "> ") "connected"
- forM_ groupLinkId $ \_ -> probeMatchingContacts ct $ contactConnIncognito ct
forM_ viaUserContactLink $ \userContactLinkId ->
withStore' (\db -> getUserContactLinkById db userId userContactLinkId) >>= \case
Just (UserContactLink {autoAccept = Just AutoAccept {autoReply = mc_}}, groupId_, gLinkMemRole) -> do
@@ -2903,6 +2902,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
whenUserNtfs user $ do
setActive $ ActiveG gName
showToast ("#" <> gName) "you are connected to group"
+ withStore' (\db -> getContactViaMember db user m) >>= \case
+ Nothing -> messageWarning "connected host does not have contact"
+ Just ct@Contact {activeConn = Connection {groupLinkId}} -> do
+ let connectedIncognito = contactConnIncognito ct || memberIncognito membership
+ forM_ groupLinkId $ \_ -> probeMatchingContacts ct connectedIncognito
GCInviteeMember -> do
memberConnectedChatItem gInfo m
toView $ CRJoinedGroupMember user gInfo m {memberStatus = GSMemConnected}
@@ -3825,8 +3829,9 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
probeMatch c1@Contact {contactId = cId1, profile = p1} c2@Contact {contactId = cId2, profile = p2} probe =
if profilesMatch (fromLocalProfile p1) (fromLocalProfile p2) && cId1 /= cId2
then do
- void . sendDirectContactMessage c1 $ XInfoProbeOk probe
- mergeContacts c1 c2
+ let (toCt, fromCt) = mergeToFromContacts c1 c2
+ void . sendDirectContactMessage toCt $ XInfoProbeOk probe
+ mergeContacts toCt fromCt
else messageWarning "probeMatch ignored: profiles don't match or same contact id"
xInfoProbeOk :: Contact -> Probe -> m ()
@@ -3834,9 +3839,22 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
r <- withStore' $ \db -> matchSentProbe db user c1 probe
forM_ r $ \c2@Contact {contactId = cId2} ->
if cId1 /= cId2
- then mergeContacts c1 c2
+ then do
+ let (toCt, fromCt) = mergeToFromContacts c1 c2
+ mergeContacts toCt fromCt
else messageWarning "xInfoProbeOk ignored: same contact id"
+ mergeToFromContacts :: Contact -> Contact -> (Contact, Contact)
+ mergeToFromContacts c1 c2
+ | d1 && not d2 = (c1, c2)
+ | d2 && not d1 = (c2, c1)
+ | ctCreatedAt c1 <= ctCreatedAt c2 = (c1, c2)
+ | otherwise = (c2, c1)
+ where
+ d1 = directOrUsed c1
+ d2 = directOrUsed c2
+ ctCreatedAt Contact {createdAt} = createdAt
+
-- to party accepting call
xCallInv :: Contact -> CallId -> CallInvitation -> RcvMessage -> MsgMeta -> m ()
xCallInv ct@Contact {contactId} callId CallInvitation {callType, callDhPubKey} msg@RcvMessage {sharedMsgId_} msgMeta = do
@@ -3943,9 +3961,10 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
messageError $ eventName <> ": wrong call state " <> T.pack (show $ callStateTag callState)
mergeContacts :: Contact -> Contact -> m ()
- mergeContacts c1 c2 = do
- withStore' $ \db -> mergeContactRecords db userId c1 c2
- toView $ CRContactsMerged user c1 c2
+ mergeContacts toCt fromCt = do
+ deleteAgentConnectionAsync user $ aConnId $ contactConn fromCt
+ withStore' $ \db -> mergeContactRecords db userId toCt fromCt
+ toView $ CRContactsMerged user toCt fromCt
saveConnInfo :: Connection -> ConnInfo -> m ()
saveConnInfo activeConn connInfo = do
diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs
index 12ac37032..e6b7a3805 100644
--- a/src/Simplex/Chat/Store.hs
+++ b/src/Simplex/Chat/Store.hs
@@ -1708,9 +1708,8 @@ matchSentProbe db user@User {userId} _from@Contact {contactId} (Probe probe) = d
cId : _ -> eitherToMaybe <$> runExceptT (getContact db user cId)
mergeContactRecords :: DB.Connection -> UserId -> Contact -> Contact -> IO ()
-mergeContactRecords db userId ct1 ct2 = do
- let (toCt, fromCt) = toFromContacts ct1 ct2
- Contact {contactId = toContactId} = toCt
+mergeContactRecords db userId toCt fromCt = do
+ let Contact {contactId = toContactId} = toCt
Contact {contactId = fromContactId, localDisplayName} = fromCt
currentTs <- getCurrentTime
-- TODO next query fixes incorrect unused contacts deletion; consider more thorough fix
@@ -1719,10 +1718,6 @@ mergeContactRecords db userId ct1 ct2 = do
db
"UPDATE contacts SET contact_used = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?"
(currentTs, userId, toContactId)
- DB.execute
- db
- "UPDATE connections SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
- (toContactId, currentTs, fromContactId, userId)
DB.execute
db
"UPDATE connections SET via_contact = ?, updated_at = ? WHERE via_contact = ? AND user_id = ?"
@@ -1735,6 +1730,10 @@ mergeContactRecords db userId ct1 ct2 = do
db
"UPDATE chat_items SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
(toContactId, currentTs, fromContactId, userId)
+ DB.execute
+ db
+ "UPDATE chat_item_reactions SET contact_id = ?, updated_at = ? WHERE contact_id = ?"
+ (toContactId, currentTs, fromContactId)
DB.executeNamed
db
[sql|
@@ -1754,17 +1753,6 @@ mergeContactRecords db userId ct1 ct2 = do
deleteContactProfile_ db userId fromContactId
DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId)
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId)
- where
- toFromContacts :: Contact -> Contact -> (Contact, Contact)
- toFromContacts c1 c2
- | d1 && not d2 = (c1, c2)
- | d2 && not d1 = (c2, c1)
- | ctCreatedAt c1 <= ctCreatedAt c2 = (c1, c2)
- | otherwise = (c2, c1)
- where
- d1 = directOrUsed c1
- d2 = directOrUsed c2
- ctCreatedAt Contact {createdAt} = createdAt
getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
getConnectionEntity db user@User {userId, userContactId} agentConnId = do
@@ -2364,6 +2352,7 @@ createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Co
:. (userId, localDisplayName, contactId, localProfileId profile, createdAt, createdAt)
)
+-- this method differs from getViaGroupContact in that it does not join with groups on contacts.via_group
getContactViaMember :: DB.Connection -> User -> GroupMember -> IO (Maybe Contact)
getContactViaMember db user@User {userId} GroupMember {groupMemberId} =
maybeFirstRow (toContact user) $
diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs
index c13318250..70fb01a13 100644
--- a/src/Simplex/Chat/View.hs
+++ b/src/Simplex/Chat/View.hs
@@ -116,11 +116,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, testView} liveItems ts
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
CRGroupsList u gs -> ttyUser u $ viewGroupsList gs
- CRSentGroupInvitation u g c _ ->
- ttyUser u $
- if viaGroupLink . contactConn $ c
- then [ttyContact' c <> " invited to group " <> ttyGroup' g <> " via your group link"]
- else ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c]
+ CRSentGroupInvitation u g c _ -> ttyUser u ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c]
CRFileTransferStatus u ftStatus -> ttyUser u $ viewFileTransferStatus ftStatus
CRFileTransferStatusXFTP u ci -> ttyUser u $ viewFileTransferStatusXFTP ci
CRUserProfile u p -> ttyUser u $ viewUserProfile p
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 305f1620c..860826e8c 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -1544,7 +1544,7 @@ testGroupLink =
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
- alice <## "bob invited to group #team via your group link"
+ alice <## "invitation to join the group #team sent to bob"
alice <## "#team: bob joined the group",
do
bob <## "alice (Alice): contact is connected"
@@ -1580,7 +1580,7 @@ testGroupLink =
<### [ "cath_1 (Catherine): contact is connected",
"contact cath_1 is merged into cath",
"use @cath to send messages",
- EndsWith "invited to group #team via your group link",
+ StartsWith "invitation to join the group #team sent to cath",
EndsWith "joined the group"
],
cath
@@ -1634,7 +1634,7 @@ testGroupLinkDeleteGroupRejoin =
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
- alice <## "bob invited to group #team via your group link"
+ alice <## "invitation to join the group #team sent to bob"
alice <## "#team: bob joined the group",
do
bob <## "alice (Alice): contact is connected"
@@ -1660,7 +1660,7 @@ testGroupLinkDeleteGroupRejoin =
<### [ "bob_1 (Bob): contact is connected",
"contact bob_1 is merged into bob",
"use @bob to send messages",
- EndsWith "invited to group #team via your group link",
+ StartsWith "invitation to join the group #team sent to bob",
EndsWith "joined the group"
],
bob
@@ -1690,7 +1690,7 @@ testGroupLinkContactUsed =
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
- alice <## "bob invited to group #team via your group link"
+ alice <## "invitation to join the group #team sent to bob"
alice <## "#team: bob joined the group",
do
bob <## "alice (Alice): contact is connected"
@@ -1752,7 +1752,7 @@ testGroupLinkIncognitoMembership =
[ do
bob <## ("cath (Catherine): contact is connected, your incognito profile for this contact is " <> bobIncognito)
bob <## "use /i cath to print out this incognito profile again"
- bob <## "cath invited to group #team via your group link"
+ bob <## "invitation to join the group #team sent to cath"
bob <## "#team: cath joined the group",
do
cath <## (bobIncognito <> ": contact is connected")
@@ -1778,7 +1778,7 @@ testGroupLinkIncognitoMembership =
[ do
bob <## (danIncognito <> ": contact is connected, your incognito profile for this contact is " <> bobIncognito)
bob <## ("use /i " <> danIncognito <> " to print out this incognito profile again")
- bob <## (danIncognito <> " invited to group #team via your group link")
+ bob <## ("invitation to join the group #team sent to " <> danIncognito)
bob <## ("#team: " <> danIncognito <> " joined the group"),
do
dan <## (bobIncognito <> ": contact is connected, your incognito profile for this contact is " <> danIncognito)
@@ -1841,7 +1841,7 @@ testGroupLinkUnusedHostContactDeleted =
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
- alice <## "bob invited to group #team via your group link"
+ alice <## "invitation to join the group #team sent to bob"
alice <## "#team: bob joined the group",
do
bob <## "alice (Alice): contact is connected"
@@ -1861,7 +1861,7 @@ testGroupLinkUnusedHostContactDeleted =
<### [ "bob_1 (Bob): contact is connected",
"contact bob_1 is merged into bob",
"use @bob to send messages",
- EndsWith "invited to group #club via your group link",
+ StartsWith "invitation to join the group #club sent to bob",
EndsWith "joined the group"
],
bob
@@ -1936,7 +1936,7 @@ testGroupLinkIncognitoUnusedHostContactsDeleted =
concurrentlyN_
[ do
alice <## (bobIncognito <> ": contact is connected")
- alice <## (bobIncognito <> " invited to group #" <> group <> " via your group link")
+ alice <## ("invitation to join the group #" <> group <> " sent to " <> bobIncognito)
alice <## ("#" <> group <> ": " <> bobIncognito <> " joined the group"),
do
bob <## (bobsAliceContact <> " (Alice): contact is connected, your incognito profile for this contact is " <> bobIncognito)
@@ -1973,7 +1973,7 @@ testGroupLinkMemberRole =
concurrentlyN_
[ do
alice <## "bob (Bob): contact is connected"
- alice <## "bob invited to group #team via your group link"
+ alice <## "invitation to join the group #team sent to bob"
alice <## "#team: bob joined the group",
do
bob <## "alice (Alice): contact is connected"
@@ -1986,12 +1986,11 @@ testGroupLinkMemberRole =
cath ##> ("/c " <> gLink)
cath <## "connection request sent!"
alice <## "cath (Catherine): accepting request to join group #team..."
- -- if contact existed it is merged
concurrentlyN_
[ alice
<### [ "cath (Catherine): contact is connected",
- EndsWith "invited to group #team via your group link",
- EndsWith "joined the group"
+ "invitation to join the group #team sent to cath",
+ "#team: cath joined the group"
],
cath
<### [ "alice (Alice): contact is connected",
@@ -2041,7 +2040,7 @@ testGroupLinkLeaveDelete =
<### [ "bob_1 (Bob): contact is connected",
"contact bob_1 is merged into bob",
"use @bob to send messages",
- EndsWith "invited to group #team via your group link",
+ StartsWith "invitation to join the group #team sent to bob",
EndsWith "joined the group"
],
bob
@@ -2057,7 +2056,7 @@ testGroupLinkLeaveDelete =
concurrentlyN_
[ alice
<### [ "cath (Catherine): contact is connected",
- "cath invited to group #team via your group link",
+ "invitation to join the group #team sent to cath",
"#team: cath joined the group"
],
cath
diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs
index db8d63457..58b80fa1e 100644
--- a/tests/ChatTests/Utils.hs
+++ b/tests/ChatTests/Utils.hs
@@ -267,7 +267,11 @@ cc <##.. ls = do
unless prefix $ print ("expected to start from one of: " <> show ls, ", got: " <> l)
prefix `shouldBe` True
-data ConsoleResponse = ConsoleString String | WithTime String | EndsWith String
+data ConsoleResponse
+ = ConsoleString String
+ | WithTime String
+ | EndsWith String
+ | StartsWith String
deriving (Show)
instance IsString ConsoleResponse where fromString = ConsoleString
@@ -287,6 +291,7 @@ getInAnyOrder f cc ls = do
ConsoleString s -> l == s
WithTime s -> dropTime_ l == Just s
EndsWith s -> s `isSuffixOf` l
+ StartsWith s -> s `isPrefixOf` l
(<###) :: HasCallStack => TestCC -> [ConsoleResponse] -> Expectation
(<###) = getInAnyOrder id