From 768928a83e2b345c7644ba88fd774c98bf8ad695 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:25:54 +0400 Subject: [PATCH] member connection wip --- src/Simplex/Chat/Library/Commands.hs | 25 ++++++++++--- src/Simplex/Chat/Library/Subscriber.hs | 21 +++++------ src/Simplex/Chat/Store/Groups.hs | 36 +++++++++++++++++-- .../Migrations/M20251018_chat_relays.hs | 7 ++++ src/Simplex/Chat/View.hs | 2 +- 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 726ce9b2c3..a8ff929a98 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1984,8 +1984,25 @@ processChatCommand vr nm = \case 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 + gVar <- asks random + -- TODO [relays] member: TBC concurrently/catch on each + forM_ relays $ \relayLink -> do + -- TODO [relays] member: check relay record already exists -> then decide based on connection + relayMember <- withFastStore $ \db -> createRelayForMember db vr gVar user gInfo + -- TODO [relays] member: pass same incognito profile to all relays + -- TODO - TBC welcomeSharedMsgId + (cReq, _cData) <- getShortLinkConnReq nm user relayLink -- this is network operation - consider recovery + let relayLinkToConnect = CCLink cReq (Just relayLink) + r <- connectViaContact user (Just $ PCEGroup gInfo relayMember) incognito relayLinkToConnect Nothing Nothing `catchAllErrors` \e -> do + -- TODO [relays] member: lock ability to change profile (see below) + throwError e + case r of + CVRSentInvitation _conn _customUserProfile -> do + _gInfo' <- withFastStore $ \db -> do + liftIO $ setPreparedGroupStartedConnection db groupId + getGroupInfo db vr user groupId + pure relayMember -- what should be in returned event? + CVRConnectedContact _ct -> pure relayMember -- error? ok_ | otherwise -> do msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of @@ -3605,9 +3622,9 @@ processChatCommand vr nm = \case -- TODO [relays] owner: replace with async join (joinConnectionAsync currently prohibited for contact links) -- TODO - or make "add relays" api retriable, via prepared connection connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff - (relayMember, conn, groupRelay) <- withStore $ \db -> do + (relayMember, conn, groupRelay) <- withFastStore $ \db -> do groupRelay <- createGroupRelayRecord db gInfo relay - relayMember <- createRelayMemberRecord db vr gVar user gInfo relay groupRelay + relayMember <- createRelayForOwner db vr gVar user gInfo relay groupRelay conn <- createRelayConnection db vr user (groupMemberId' relayMember) connId ConnPrepared chatV subMode pure (relayMember, conn, groupRelay) let GroupMember {memberRole = userRole, memberId = userMemberId} = membership diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 3d001a6481..c308306a38 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -828,7 +828,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO [relays] owner: relay added chat item? toView $ CEvtRelayJoined user gInfo m'' gLink relays Nothing -> messageError "x.grp.relay.acpt: group link not updated" - Nothing -> do -- TODO [relays] relay: don't introduce new member to other members + Nothing -> do (gInfo', mStatus) <- if not (memberPending m) then do @@ -847,15 +847,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let Connection {viaUserContactLink} = conn when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo'' when (connChatVersion < batchSend2Version) $ getAutoReplyMsg >>= mapM_ (\mc -> sendGroupAutoReply mc Nothing) - case mStatus of - GSMemPendingApproval -> pure () - GSMemPendingReview -> introduceToModerators vr user gInfo'' m' - _ -> do - introduceToAll vr user gInfo'' m' - let memberIsCustomer = case businessChat gInfo'' of - Just BusinessChatInfo {chatType = BCCustomer, customerId} -> memberId' m' == customerId - _ -> False - when (groupFeatureAllowed SGFHistory gInfo'' && not memberIsCustomer) $ sendHistory user gInfo'' m' + unless (isRelay' membership) $ + case mStatus of + GSMemPendingApproval -> pure () + GSMemPendingReview -> introduceToModerators vr user gInfo'' m' + _ -> do + introduceToAll vr user gInfo'' m' + let memberIsCustomer = case businessChat gInfo'' of + Just BusinessChatInfo {chatType = BCCustomer, customerId} -> memberId' m' == customerId + _ -> False + when (groupFeatureAllowed SGFHistory gInfo'' && not memberIsCustomer) $ sendHistory user gInfo'' m' where sendXGrpLinkMem gInfo'' = do let incognitoProfile = ExistingIncognito <$> incognitoMembershipProfile gInfo'' diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 92f11b5cfc..82a30c5d47 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -77,7 +77,8 @@ module Simplex.Chat.Store.Groups createGroupRelayRecord, getGroupRelayById, getGroupRelays, - createRelayMemberRecord, + createRelayForOwner, + createRelayForMember, createRelayConnection, updateRelayStatus, updateRelayStatusFromTo, @@ -1179,8 +1180,9 @@ toGroupRelay (groupRelayId, userChatRelayId, relayStatus, relayLink) = -- TODO - GCInviteeMember -> GCRelayMember? -- TODO - GRMember -> GRRelay? -- TODO - create 1 profile per relay, link to chat_relays? -createRelayMemberRecord :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> GroupRelay -> ExceptT StoreError IO GroupMember -createRelayMemberRecord db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} GroupRelay {groupRelayId} = do +-- TODO - retrieve profile from relay address +createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> GroupRelay -> ExceptT StoreError IO GroupMember +createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} GroupRelay {groupRelayId} = do currentTs <- liftIO getCurrentTime let relayProfile = profileFromName name (localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs @@ -1202,6 +1204,34 @@ createRelayMemberRecord db vr gVar user@User {userId, userContactId} GroupInfo { insertedRowId db getGroupMemberById db vr user groupMemberId +-- TODO [relays] TBC role, category, relay profile +-- TODO - TBC retrieve profile from somewhere before connection +-- TODO - or update upon connection, as in regular prepared groups +-- TODO (current logic mimics insertHost_ from createPreparedGroup) +createRelayForMember :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> ExceptT StoreError IO GroupMember +createRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {groupId, localDisplayName = groupLDN} = do + currentTs <- liftIO getCurrentTime + randRelayId <- liftIO $ encodedRandomBytes gVar 12 + let memberId = MemberId $ encodeUtf8 groupLDN <> "_relay_" <> randRelayId <> "_unknown_id" + relayProfile = profileFromName $ nameFromMemberId memberId + (localDisplayName, profileId) <- createNewMemberProfile_ db user relayProfile currentTs + groupMemberId <- liftIO $ do + DB.execute + db + [sql| + INSERT INTO group_members + ( group_id, member_id, member_role, member_category, member_status, invited_by, + user_id, local_display_name, contact_profile_id, created_at, updated_at, + is_relay + ) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + |] + ( (groupId, memberId, GRMember, GCHostMember, GSMemAccepted, fromInvitedBy userContactId IBUnknown) + :. (userId, localDisplayName, profileId, currentTs, currentTs, BI True) + ) + insertedRowId db + getGroupMember db vr user groupId groupMemberId + createRelayConnection :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ConnId -> ConnStatus -> VersionChat -> SubscriptionMode -> ExceptT StoreError IO Connection createRelayConnection db vr user@User {userId} groupMemberId agentConnId connStatus chatV subMode = do currentTs <- liftIO getCurrentTime diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs index 34356c0092..fe72e5baa1 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs @@ -5,6 +5,13 @@ module Simplex.Chat.Store.SQLite.Migrations.M20251018_chat_relays where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) +-- TODO [relays] owner: consider flipping group_members.group_relay_id fkey to group_relays.group_member_id +-- TODO - pros: less joins in all member queries, +-- TODO - can discern relay by is_relay, +-- TODO - little places need additional relay data +-- TODO - cons: in some places will need extra query: +-- TODO - SELECT ... FROM group_relays WHERE group_member_id = ? + -- - chat_relays - user's list of chat relays to choose from (similar to protocol_servers) -- - users.is_user_chat_relay - indicates that the user can serve as a chat relay -- (TBC usage, e.g. agree to invitations to be relay) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index c5a088995d..5874256876 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2015,7 +2015,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case CPGroupLink glp -> case glp of GLPOk direct groupSLinkData -> [grpLink $ if direct then "ok to connect directly" else "ok to connect via relays"] - <> [viewJSON groupSLinkData | testView] + <> [viewJSON groupSLinkData] -- | testView] GLPOwnLink g -> [grpLink "own link for group " <> ttyGroup' g] GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"]