diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 72155d6ccb..0f318dd82a 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -49,6 +49,7 @@ library Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items Simplex.Chat.Migrations.M20220824_profiles_local_alias Simplex.Chat.Migrations.M20220909_commands + Simplex.Chat.Migrations.M20220926_connection_alias Simplex.Chat.Mobile Simplex.Chat.Options Simplex.Chat.ProfileGenerator diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d87d060174..b281da86ff 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -597,6 +597,11 @@ processChatCommand = \case ct <- getContact db userId contactId liftIO $ updateContactAlias db userId ct localAlias pure $ CRContactAliasUpdated ct' + APISetConnectionAlias connId localAlias -> withUser $ \User {userId} -> do + conn' <- withStore $ \db -> do + conn <- getPendingContactConnection db userId connId + liftIO $ updateContactConnectionAlias db userId conn localAlias + pure $ CRConnectionAliasUpdated conn' APIParseMarkdown text -> pure . CRApiParsedMarkdown $ parseMaybeMarkdownList text APIGetNtfToken -> withUser $ \_ -> crNtfToken <$> withAgent getNtfToken APIRegisterToken token mode -> CRNtfTokenStatus <$> withUser (\_ -> withAgent $ \a -> registerNtfToken a token mode) @@ -662,7 +667,7 @@ processChatCommand = \case incognito <- readTVarIO =<< asks incognitoMode incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing (connId, cReq) <- withAgent $ \a -> createConnection a True SCMInvitation - conn <- withStore' $ \db -> createDirectConnection db userId connId ConnNew incognitoProfile + conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnNew incognitoProfile toView $ CRNewContactConnection conn pure $ CRInvitation cReq Connect (Just (ACR SCMInvitation cReq)) -> withUser $ \User {userId, profile} -> withChatLock . procCmd $ do @@ -671,7 +676,7 @@ processChatCommand = \case incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing let profileToSend = fromMaybe (fromLocalProfile profile) incognitoProfile connId <- withAgent $ \a -> joinConnection a True cReq . directMessage $ XInfo profileToSend - conn <- withStore' $ \db -> createDirectConnection db userId connId ConnJoined incognitoProfile + conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnJoined incognitoProfile toView $ CRNewContactConnection conn pure CRSentConfirmation Connect (Just (ACR SCMContact cReq)) -> withUser $ \User {userId, profile} -> @@ -2709,6 +2714,7 @@ chatCommandP = "/_call get" $> APIGetCallInvitations, "/_profile " *> (APIUpdateProfile <$> jsonP), "/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")), + "/_set alias :" *> (APISetConnectionAlias <$> A.decimal <*> (A.space *> textP <|> pure "")), "/_parse " *> (APIParseMarkdown . safeDecodeUtf8 <$> A.takeByteString), "/_ntf get" $> APIGetNtfToken, "/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 51de29a118..1f856386cb 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -144,6 +144,7 @@ data ChatCommand | APICallStatus ContactId WebRTCCallStatus | APIUpdateProfile Profile | APISetContactAlias ContactId LocalAlias + | APISetConnectionAlias Int64 LocalAlias | APIParseMarkdown Text | APIGetNtfToken | APIRegisterToken DeviceToken NotificationsMode @@ -280,6 +281,7 @@ data ChatResponse | CRSndGroupFileCancelled {chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRUserProfileUpdated {fromProfile :: Profile, toProfile :: Profile} | CRContactAliasUpdated {toContact :: Contact} + | CRConnectionAliasUpdated {toConnection :: PendingContactConnection} | CRContactConnecting {contact :: Contact} | CRContactConnected {contact :: Contact, userCustomProfile :: Maybe Profile} | CRContactAnotherClient {contact :: Contact} diff --git a/src/Simplex/Chat/Migrations/M20220926_connection_alias.hs b/src/Simplex/Chat/Migrations/M20220926_connection_alias.hs new file mode 100644 index 0000000000..ede7cc3cfc --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20220926_connection_alias.hs @@ -0,0 +1,17 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20220926_connection_alias where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20220926_connection_alias :: Query +m20220926_connection_alias = + [sql| +PRAGMA ignore_check_constraints=ON; + +ALTER TABLE connections ADD COLUMN local_alias DEFAULT '' CHECK (local_alias NOT NULL); +UPDATE connections SET local_alias = ''; + +PRAGMA ignore_check_constraints=OFF; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 69d7e84eb1..fda7e3e05d 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -244,6 +244,7 @@ CREATE TABLE connections( REFERENCES user_contact_links(user_contact_link_id) ON DELETE SET NULL, custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, conn_req_inv BLOB, + local_alias DEFAULT '' CHECK(local_alias NOT NULL), FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 2d091d4763..37a42b54ab 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -40,6 +40,7 @@ module Simplex.Chat.Store updateUserProfile, updateContactProfile, updateContactAlias, + updateContactConnectionAlias, getUserContacts, createUserContactLink, getUserContactLinkConnections, @@ -248,6 +249,7 @@ import Simplex.Chat.Migrations.M20220822_groups_host_conn_custom_user_profile_id import Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items import Simplex.Chat.Migrations.M20220824_profiles_local_alias import Simplex.Chat.Migrations.M20220909_commands +import Simplex.Chat.Migrations.M20220926_connection_alias import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Messaging.Agent.Protocol (ACorrId, AgentMsgId, ConnId, InvitationId, MsgMeta (..)) @@ -283,7 +285,8 @@ schemaMigrations = ("20220822_groups_host_conn_custom_user_profile_id", m20220822_groups_host_conn_custom_user_profile_id), ("20220823_delete_broken_group_event_chat_items", m20220823_delete_broken_group_event_chat_items), ("20220824_profiles_local_alias", m20220824_profiles_local_alias), - ("20220909_commands", m20220909_commands) + ("20220909_commands", m20220909_commands), + ("20220926_connection_alias", m20220926_connection_alias) ] -- | The list of migrations in ascending order by date @@ -375,7 +378,7 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile = do |] (userId, acId, pccConnStatus, ConnContact, cReqHash, xContactId, customUserProfileId, createdAt, createdAt) pccConnId <- insertedRowId db - pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, customUserProfileId, createdAt, updatedAt = createdAt} + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt} getConnReqContactXContactId :: DB.Connection -> UserId -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId) getConnReqContactXContactId db userId cReqHash = do @@ -393,7 +396,7 @@ getConnReqContactXContactId db userId cReqHash = do -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.enable_ntfs, ct.created_at, ct.updated_at, -- Connection - c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, + c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id @@ -411,19 +414,19 @@ getConnReqContactXContactId db userId cReqHash = do "SELECT xcontact_id FROM connections WHERE user_id = ? AND via_contact_uri_hash = ? LIMIT 1" (userId, cReqHash) -createDirectConnection :: DB.Connection -> UserId -> ConnId -> ConnStatus -> Maybe Profile -> IO PendingContactConnection -createDirectConnection db userId acId pccConnStatus incognitoProfile = do +createDirectConnection :: DB.Connection -> UserId -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> IO PendingContactConnection +createDirectConnection db userId acId cReq pccConnStatus incognitoProfile = do createdAt <- getCurrentTime customUserProfileId <- createIncognitoProfile_ db userId createdAt incognitoProfile DB.execute db [sql| INSERT INTO connections - (user_id, agent_conn_id, conn_status, conn_type, custom_user_profile_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?) + (user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, custom_user_profile_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) |] - (userId, acId, pccConnStatus, ConnContact, customUserProfileId, createdAt, createdAt) + (userId, acId, cReq, pccConnStatus, ConnContact, customUserProfileId, createdAt, createdAt) pccConnId <- insertedRowId db - pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, customUserProfileId, createdAt, updatedAt = createdAt} + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt} createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Maybe Profile -> IO (Maybe Int64) createIncognitoProfile_ db userId createdAt incognitoProfile = @@ -466,23 +469,23 @@ createConnection_ db userId connType entityId acId viaContact viaUserContactLink :. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs) ) connId <- insertedRowId db - pure Connection {connId, agentConnId = AgentConnId acId, connType, entityId, viaContact, viaUserContactLink, customUserProfileId, connLevel, connStatus = ConnNew, createdAt = currentTs} + pure Connection {connId, agentConnId = AgentConnId acId, connType, entityId, viaContact, viaUserContactLink, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs} where ent ct = if connType == ct then entityId else Nothing createDirectContact :: DB.Connection -> UserId -> Connection -> Profile -> ExceptT StoreError IO Contact -createDirectContact db userId activeConn@Connection {connId} profile = do +createDirectContact db userId activeConn@Connection {connId, localAlias} profile = do createdAt <- liftIO getCurrentTime - (localDisplayName, contactId, profileId) <- createContact_ db userId connId profile Nothing createdAt + (localDisplayName, contactId, profileId) <- createContact_ db userId connId profile localAlias Nothing createdAt pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn, viaGroup = Nothing, chatSettings = defaultChatSettings, createdAt, updatedAt = createdAt} -createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) -createContact_ db userId connId Profile {displayName, fullName, image} viaGroup currentTs = +createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> LocalAlias -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) +createContact_ db userId connId Profile {displayName, fullName, image} localAlias viaGroup currentTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" - (displayName, fullName, image, userId, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, user_id, local_alias, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" + (displayName, fullName, image, userId, localAlias, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -578,7 +581,20 @@ updateContactAlias db userId c@Contact {profile = lp@LocalProfile {profileId}} l WHERE user_id = ? AND contact_profile_id = ? |] (localAlias, updatedAt, userId, profileId) - pure $ (c :: Contact) {profile = lp {localAlias = localAlias}} + pure $ (c :: Contact) {profile = lp {localAlias}} + +updateContactConnectionAlias :: DB.Connection -> UserId -> PendingContactConnection -> LocalAlias -> IO PendingContactConnection +updateContactConnectionAlias db userId conn localAlias = do + updatedAt <- getCurrentTime + DB.execute + db + [sql| + UPDATE connections + SET local_alias = ?, updated_at = ? + WHERE user_id = ? AND connection_id = ? + |] + (localAlias, updatedAt, userId, pccConnId conn) + pure (conn :: PendingContactConnection) {localAlias} updateContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO () updateContactProfile_ db userId profileId profile = do @@ -661,7 +677,7 @@ getUserContactLinks db User {userId} = db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, uc.user_contact_link_id, uc.conn_req_contact FROM connections c JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id @@ -805,7 +821,7 @@ createOrUpdateContactRequest db userId userContactLinkId invId Profile {displayN -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.enable_ntfs, ct.created_at, ct.updated_at, -- Connection - c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, + c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id @@ -980,7 +996,7 @@ getPendingContactConnections db User {userId} = do <$> DB.queryNamed db [sql| - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = :user_id AND conn_type = :conn_type @@ -997,7 +1013,7 @@ getContactConnections db userId Contact {contactId} = db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM connections c JOIN contacts ct ON ct.contact_id = c.contact_id WHERE c.user_id = ? AND ct.user_id = ? AND ct.contact_id = ? @@ -1008,14 +1024,14 @@ getContactConnections db userId Contact {contactId} = type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64) -type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Maybe Int64, ConnStatus, ConnType) :. EntityIdsRow :. Only UTCTime +type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Maybe Int64, ConnStatus, ConnType, LocalAlias) :. EntityIdsRow :. Only UTCTime -type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Int64, Maybe ConnStatus, Maybe ConnType) :. EntityIdsRow :. Only (Maybe UTCTime) +type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe LocalAlias) :. EntityIdsRow :. Only (Maybe UTCTime) toConnection :: ConnectionRow -> Connection -toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only createdAt) = +toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only createdAt) = let entityId = entityId_ connType - in Connection {connId, agentConnId = AgentConnId acId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType, entityId, createdAt} + in Connection {connId, agentConnId = AgentConnId acId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType, localAlias, entityId, createdAt} where entityId_ :: ConnType -> Maybe Int64 entityId_ ConnContact = contactId @@ -1025,8 +1041,8 @@ toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, customUs entityId_ ConnUserContact = userContactLinkId toMaybeConnection :: MaybeConnectionRow -> Maybe Connection -toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, customUserProfileId, Just connStatus, Just connType) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only (Just createdAt)) = - Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only createdAt) +toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, customUserProfileId, Just connStatus, Just connType, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only (Just createdAt)) = + Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. Only createdAt) toMaybeConnection _ = Nothing getMatchingContacts :: DB.Connection -> UserId -> Contact -> IO [Contact] @@ -1193,7 +1209,7 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do db [sql| SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, custom_user_profile_id, - conn_status, conn_type, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at + conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at FROM connections WHERE user_id = ? AND agent_conn_id = ? |] @@ -1290,7 +1306,7 @@ getConnectionById db User {userId} connId = ExceptT $ do db [sql| SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, custom_user_profile_id, - conn_status, conn_type, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at + conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at FROM connections WHERE user_id = ? AND connection_id = ? |] @@ -1335,7 +1351,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId = m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) JOIN groups g ON g.group_id = m.group_id @@ -1561,7 +1577,7 @@ getGroupMember db user@User {userId} groupId groupMemberId = m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN connections c ON c.connection_id = ( @@ -1583,7 +1599,7 @@ getGroupMembers db user@User {userId, userContactId} GroupInfo {groupId} = do m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN connections c ON c.connection_id = ( @@ -1861,7 +1877,7 @@ createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupM currentTs <- liftIO getCurrentTime Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId memberContactId Nothing customUserProfileId cLevel currentTs liftIO $ setCommandConnId db user directCmdId directConnId - (localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile (Just groupId) currentTs + (localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile "" (Just groupId) currentTs liftIO $ do let newMember = NewGroupMember @@ -1935,7 +1951,7 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} = m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM group_members m JOIN contacts ct ON ct.contact_id = m.contact_id JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) @@ -1967,7 +1983,7 @@ getViaGroupContact db User {userId} GroupMember {groupMemberId} = SELECT ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.enable_ntfs, ct.created_at, ct.updated_at, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at + c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM contacts ct JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id JOIN connections c ON c.connection_id = ( @@ -2772,7 +2788,7 @@ getDirectChatPreviews_ db User {userId} = do -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.enable_ntfs, ct.created_at, ct.updated_at, -- Connection - c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, + c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, -- ChatStats COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), @@ -2921,13 +2937,13 @@ getContactConnectionChatPreviews_ db User {userId} _ = <$> DB.query db [sql| - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL |] (userId, ConnContact) where - toContactConnectionChatPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, UTCTime, UTCTime) -> AChat + toContactConnectionChatPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChat toContactConnectionChatPreview connRow = let conn = toPendingContactConnection connRow stats = ChatStats {unreadCount = 0, minUnreadItemId = 0} @@ -2939,7 +2955,7 @@ getPendingContactConnection db userId connId = do DB.query db [sql| - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND connection_id = ? @@ -2973,9 +2989,9 @@ updateGroupSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () updateGroupSettings db User {userId} groupId ChatSettings {enableNtfs} = DB.execute db "UPDATE groups SET enable_ntfs = ? WHERE user_id = ? AND group_id = ?" (enableNtfs, userId, groupId) -toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, UTCTime, UTCTime) -> PendingContactConnection -toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, customUserProfileId, createdAt, updatedAt) = - PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, customUserProfileId, createdAt, updatedAt} +toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> PendingContactConnection +toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt) = + PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt} getDirectChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect) getDirectChat db user contactId pagination search_ = do @@ -3112,7 +3128,7 @@ getContact db userId contactId = -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.enable_ntfs, ct.created_at, ct.updated_at, -- Connection - c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, + c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 8ddf06feae..9248c6f059 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -760,6 +760,7 @@ data Connection = Connection customUserProfileId :: Maybe Int64, connType :: ConnType, connStatus :: ConnStatus, + localAlias :: Text, entityId :: Maybe Int64, -- contact, group member, file ID or user contact ID createdAt :: UTCTime } @@ -779,6 +780,8 @@ data PendingContactConnection = PendingContactConnection viaContactUri :: Bool, viaUserContactLink :: Maybe Int64, customUserProfileId :: Maybe Int64, + connReqInv :: Maybe ConnReqInvitation, + localAlias :: Text, createdAt :: UTCTime, updatedAt :: UTCTime } diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2086305797..abefa88407 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -118,6 +118,7 @@ responseToView testView = \case CRRcvFileCancelled ft -> receivingFile_ "cancelled" ft CRUserProfileUpdated p p' -> viewUserProfileUpdated p p' CRContactAliasUpdated c -> viewContactAliasUpdated c + CRConnectionAliasUpdated c -> viewConnectionAliasUpdated c CRContactUpdated c c' -> viewContactUpdated c c' CRContactsMerged intoCt mergedCt -> viewContactsMerged intoCt mergedCt CRReceivedContactRequest UserContactRequest {localDisplayName = c, profile} -> viewReceivedContactRequest c profile @@ -389,11 +390,14 @@ viewContactsList :: [Contact] -> [StyledString] viewContactsList = let ldn = T.toLower . (localDisplayName :: Contact -> ContactName) incognito ct = if contactConnIncognito ct then incognitoPrefix else "" - in map (\ct -> incognito ct <> ttyFullContact ct <> muted ct) . sortOn ldn + in map (\ct -> incognito ct <> ttyFullContact ct <> muted ct <> alias ct) . sortOn ldn where muted Contact {chatSettings, localDisplayName = ldn} | enableNtfs chatSettings = "" | otherwise = " (muted, you can " <> highlight ("/unmute @" <> ldn) <> ")" + alias Contact {profile = LocalProfile {localAlias}} + | localAlias == "" = "" + | otherwise = " (alias: " <> plain localAlias <> ")" viewUserContactLinkDeleted :: [StyledString] viewUserContactLinkDeleted = @@ -635,6 +639,11 @@ viewContactAliasUpdated Contact {localDisplayName = n, profile = LocalProfile {l | localAlias == "" = ["contact " <> ttyContact n <> " alias removed"] | otherwise = ["contact " <> ttyContact n <> " alias updated: " <> plain localAlias] +viewConnectionAliasUpdated :: PendingContactConnection -> [StyledString] +viewConnectionAliasUpdated PendingContactConnection {pccConnId, localAlias} + | localAlias == "" = ["connection " <> sShow pccConnId <> " alias removed"] + | otherwise = ["connection " <> sShow pccConnId <> " alias updated: " <> plain localAlias] + viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated Contact {localDisplayName = n, profile = LocalProfile {fullName}} diff --git a/tests/ChatTests.hs b/tests/ChatTests.hs index 0d49bf109b..e92b0c8e8f 100644 --- a/tests/ChatTests.hs +++ b/tests/ChatTests.hs @@ -94,7 +94,9 @@ chatTests = do it "accept contact request incognito" testAcceptContactRequestIncognito it "join group incognito" testJoinGroupIncognito it "can't invite contact to whom user connected incognito to a group" testCantInviteContactIncognito + describe "contact aliases" $ do it "set contact alias" testSetAlias + it "set connection alias" testSetConnectionAlias describe "SMP servers" $ it "get and set SMP servers" testGetSetSMPServers describe "async connection handshake" $ do @@ -2372,7 +2374,28 @@ testSetAlias = testChat2 aliceProfile bobProfile $ \alice bob -> do connectUsers alice bob alice #$> ("/_set alias @2 my friend bob", id, "contact bob alias updated: my friend bob") + alice ##> "/cs" + alice <## "bob (Bob) (alias: my friend bob)" alice #$> ("/_set alias @2", id, "contact bob alias removed") + alice ##> "/cs" + alice <## "bob (Bob)" + +testSetConnectionAlias :: IO () +testSetConnectionAlias = testChat2 aliceProfile bobProfile $ + \alice bob -> do + alice ##> "/c" + inv <- getInvitation alice + alice @@@ [(":1","")] + alice ##> "/_set alias :1 friend" + alice <## "connection 1 alias updated: friend" + bob ##> ("/c " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## ("bob (Bob): contact is connected")) + (bob <## ("alice (Alice): contact is connected")) + alice @@@ [("@bob","")] + alice ##> "/cs" + alice <## "bob (Bob) (alias: friend)" testGetSetSMPServers :: IO () testGetSetSMPServers = @@ -2419,8 +2442,8 @@ testFullAsync = withTmpFiles $ do alice ##> "/c" getInvitation alice withNewTestChat "bob" bobProfile $ \bob -> do - bob ##> ("/c " <> inv) - bob <## "confirmation sent!" + bob `send` ("/c " <> inv) + bob <### ["/c " <> inv, "confirmation sent!"] withTestChat "alice" $ \_ -> pure () -- connecting... notification in UI withTestChat "bob" $ \_ -> pure () -- connecting... notification in UI withTestChat "alice" $ \alice -> do