From fdf4cf91560ba05fe7f8d172be4e84a5f44c0f87 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:15:10 +0400 Subject: [PATCH] member connection wip --- bots/api/TYPES.md | 1 + .../types/typescript/src/types.ts | 1 + src/Simplex/Chat/Library/Commands.hs | 69 +++++++++++-------- src/Simplex/Chat/Store/Groups.hs | 16 ++--- .../SQLite/Migrations/chat_query_plans.txt | 15 ++-- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 6d277a44b3..62511e7a85 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -2179,6 +2179,7 @@ MemberSupport: Ok: - type: "ok" +- direct: bool - groupSLinkData_: [GroupShortLinkData](#groupshortlinkdata)? OwnLink: diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 41a0641746..7e0905caab 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2468,6 +2468,7 @@ export namespace GroupLinkPlan { export interface Ok extends Interface { type: "ok" + direct: boolean groupSLinkData_?: GroupShortLinkData } diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 5519b4f5da..92cc8fca36 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1873,7 +1873,7 @@ processChatCommand vr nm = \case let Profile {preferences} = profile groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences groupProfile = businessGroupProfile profile groupPreferences - (gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user groupProfile True ccLink welcomeSharedMsgId + (gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user groupProfile True ccLink welcomeSharedMsgId False void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart) let cd = CDGroupRcv gInfo Nothing hostMember createItem sharedMsgId content = createChatItem user cd True content sharedMsgId Nothing @@ -1900,11 +1900,11 @@ processChatCommand vr nm = \case APIPrepareGroup userId ccLink direct groupSLinkData -> withUserId userId $ \user -> do let GroupShortLinkData {groupProfile = gp@GroupProfile {description}} = groupSLinkData welcomeSharedMsgId <- forM description $ \_ -> getSharedMsgId - -- TODO [relays] member: create group as with useRelays = not direct - -- TODO - repeat retrieving link data in APIConnectPreparedGroup, connect to relays - -- TODO - hostMember to later be associated with owner profile - (gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user gp False ccLink welcomeSharedMsgId + let useRelays = not direct + (gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user gp False ccLink welcomeSharedMsgId useRelays void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart) + -- TODO [relays] member: TBC save items as message from channel + -- TODO - hostMember to later be associated with owner profile when relays send it let cd = CDGroupRcv gInfo Nothing hostMember cInfo = GroupChat gInfo Nothing void $ createGroupFeatureItems_ user cd True CIRcvGroupFeature gInfo @@ -1978,30 +1978,39 @@ processChatCommand vr nm = \case (gInfo, hostMember) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getHostMember db vr user groupId case preparedGroup gInfo of Nothing -> throwCmdError "group doesn't have link to connect" - Just PreparedGroup {connLinkToConnect, welcomeSharedMsgId, requestSharedMsgId} -> do - msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of - Just smId -> pure (smId, mc) - Nothing -> do - smId <- getSharedMsgId - withFastStore' $ \db -> setRequestSharedMsgIdForGroup db groupId smId - pure (smId, mc) - r <- connectViaContact user (Just $ PCEGroup gInfo hostMember) incognito connLinkToConnect welcomeSharedMsgId msg_ `catchAllErrors` \e -> do - -- get updated group info, in case connection was started (connLinkPreparedConnection) - in UI it would lock ability to change - -- user or incognito profile for group or business chat, in case server received request while client got network error - gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId - toView $ CEvtChatInfoUpdated user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing) - throwError e - case r of - CVRSentInvitation _conn customUserProfile -> do - -- get updated group info (connLinkStartedConnection and incognito membership) - gInfo' <- withFastStore $ \db -> do - liftIO $ setPreparedGroupStartedConnection db groupId - getGroupInfo db vr user groupId - forM_ msg_ $ \(sharedMsgId, mc) -> do - ci <- createChatItem user (CDGroupSnd gInfo' Nothing) False (CISndMsgContent mc) (Just sharedMsgId) Nothing - toView $ CEvtNewChatItems user [ci] - pure $ CRStartedConnectionToGroup user gInfo' customUserProfile - CVRConnectedContact _ct -> throwChatError $ CEException "contact already exists when connecting to group" + Just PreparedGroup {connLinkToConnect, welcomeSharedMsgId, requestSharedMsgId} + | useRelays' gInfo -> do + sLnk <- case toShortLinkContact connLinkToConnect of + Just sl -> pure sl + Nothing -> throwChatError $ CEException "failed to retrieve relays: no short link" + (_cReq, _cData@(ContactLinkData _ UserContactData {relays})) <- getShortLinkConnReq nm user sLnk + liftIO $ print $ "retrieved relays: " <> show relays + -- TODO [relays] member: connect to all relays + ok_ + | otherwise -> do + msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of + Just smId -> pure (smId, mc) + Nothing -> do + smId <- getSharedMsgId + withFastStore' $ \db -> setRequestSharedMsgIdForGroup db groupId smId + pure (smId, mc) + r <- connectViaContact user (Just $ PCEGroup gInfo hostMember) incognito connLinkToConnect welcomeSharedMsgId msg_ `catchAllErrors` \e -> do + -- get updated group info, in case connection was started (connLinkPreparedConnection) - in UI it would lock ability to change + -- user or incognito profile for group or business chat, in case server received request while client got network error + gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId + toView $ CEvtChatInfoUpdated user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing) + throwError e + case r of + CVRSentInvitation _conn customUserProfile -> do + -- get updated group info (connLinkStartedConnection and incognito membership) + gInfo' <- withFastStore $ \db -> do + liftIO $ setPreparedGroupStartedConnection db groupId + getGroupInfo db vr user groupId + forM_ msg_ $ \(sharedMsgId, mc) -> do + ci <- createChatItem user (CDGroupSnd gInfo' Nothing) False (CISndMsgContent mc) (Just sharedMsgId) Nothing + toView $ CEvtNewChatItems user [ci] + pure $ CRStartedConnectionToGroup user gInfo' customUserProfile + CVRConnectedContact _ct -> throwChatError $ CEException "contact already exists when connecting to group" APIConnect userId incognito (Just acl) -> withUserId userId $ \user -> case acl of ACCL SCMInvitation ccLink -> do (conn, incognitoProfile) <- connectViaInvitation user incognito ccLink Nothing @@ -4647,7 +4656,7 @@ chatCommandP = ("/help" <|> "/h") $> ChatHelp HSMain, ("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile), "/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP), - ("/public group") *> (NewPublicGroup <$> incognitoP <*> _strP <* A.space <* char_ '#' <*> groupProfile), + ("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> groupProfile), "/_public group " *> (APINewPublicGroup <$> A.decimal <*> incognitoOnOffP <*> _strP <* A.space <*> jsonP), ("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)), ("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayNameP <*> (" mute" $> MFNone <|> pure MFAll)), diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 84fdb452c4..92f11b5cfc 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -541,11 +541,11 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile { DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId) DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId) -createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> ExceptT StoreError IO (GroupInfo, GroupMember) -createPreparedGroup db vr user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId = do +createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> ExceptT StoreError IO (GroupInfo, GroupMember) +createPreparedGroup db vr user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays = do currentTs <- liftIO getCurrentTime let prepared = Just (connLinkToConnect, welcomeSharedMsgId) - (groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing Nothing currentTs + (groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing useRelays Nothing currentTs hostMemberId <- insertHost_ currentTs groupId groupLDN let userMember = MemberIdRole (MemberId $ encodeUtf8 groupLDN <> "_user_unknown_id") GRMember membership <- createContactMemberInv_ db user groupId (Just hostMemberId) user userMember GCUserMember GSMemUnknown IBUnknown Nothing False currentTs vr @@ -742,7 +742,7 @@ createGroupViaLink' business membershipStatus = do currentTs <- liftIO getCurrentTime - (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing business Nothing currentTs + (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing business False Nothing currentTs hostMemberId <- insertHost_ currentTs groupId liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId) -- using IBUnknown since host is created without contact @@ -767,8 +767,8 @@ createGroupViaLink' ) insertedRowId db -createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> Maybe RelayStatus -> UTCTime -> ExceptT StoreError IO (GroupId, Text) -createGroup_ db userId groupProfile prepared business relayOwnStatus currentTs = ExceptT $ do +createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> Bool -> Maybe RelayStatus -> UTCTime -> ExceptT StoreError IO (GroupId, Text) +createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus currentTs = ExceptT $ do let GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} = groupProfile withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do liftIO $ do @@ -786,7 +786,7 @@ createGroup_ db userId groupProfile prepared business relayOwnStatus currentTs = business_chat, business_member_id, customer_member_id, use_relays, relay_own_status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. toPreparedGroupRow prepared :. businessChatInfoRow business :. (BI $ isJust relayOwnStatus, relayOwnStatus)) + ((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. toPreparedGroupRow prepared :. businessChatInfoRow business :. (BI useRelays, relayOwnStatus)) groupId <- insertedRowId db pure (groupId, localDisplayName) @@ -1253,7 +1253,7 @@ setRelayLinkAccepted db relay@GroupRelay {groupRelayId} relayLink = do createGroupRelayInvitation :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> GroupRelayInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember) createGroupRelayInvitation db vr user@User {userId} groupProfile GroupRelayInvitation {fromMember, fromMemberProfile, invitedMember} = do currentTs <- liftIO getCurrentTime - (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing Nothing (Just RSInvited) currentTs + (groupId, _groupLDN) <- createGroup_ db userId groupProfile Nothing Nothing True (Just RSInvited) currentTs ownerMemberId <- insertOwner_ currentTs groupId _membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user invitedMember GCUserMember GSMemAccepted IBUnknown Nothing True currentTs vr ownerMember <- getGroupMember db vr user groupId ownerMemberId diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 0a311fef38..87679725a2 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -983,8 +983,8 @@ Query: INSERT INTO group_members ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id, user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, - peer_chat_min_version, peer_chat_max_version) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + peer_chat_min_version, peer_chat_max_version, is_relay) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH delivery_jobs USING COVERING INDEX idx_delivery_jobs_single_sender_group_member_id (single_sender_group_member_id=?) @@ -1017,8 +1017,8 @@ Query: INSERT INTO groups (group_profile_id, local_display_name, user_id, enable_ntfs, created_at, updated_at, chat_ts, user_member_profile_sent_at, conn_full_link_to_connect, conn_short_link_to_connect, welcome_shared_msg_id, - business_chat, business_member_id, customer_member_id) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + business_chat, business_member_id, customer_member_id, use_relays, relay_own_status) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: @@ -5331,6 +5331,13 @@ Plan: SEARCH i USING COVERING INDEX idx_chat_items_notes_created_at (user_id=? AND note_folder_id=?) SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) +Query: + SELECT group_relay_id, chat_relay_id, relay_status, relay_link + FROM group_relays + WHERE group_id = ? +Plan: +SEARCH group_relays USING INDEX idx_group_relays_group_id (group_id=?) + Query: SELECT m.group_member_id, m.member_id, m.member_role, p.display_name, p.local_alias FROM group_members m