diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 004d790844..eada7e5a1b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2266,8 +2266,6 @@ processChatCommand cxt nm = \case CVRSentInvitation conn incognitoProfile -> pure $ CRSentInvitation user (mkPendingContactConnection conn Nothing) incognitoProfile APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do - -- TODO [relays] member: /c api to support groups with relays - -- TODO - possibly by going through APIPrepareGroup -> APIConnectPreparedGroup (ccLink, plan) <- connectPlan user cLink False Nothing `catchAllErrors` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing Nothing)); _ -> throwError e connectWithPlan user incognito ccLink plan Connect _ Nothing -> throwChatError CEInvalidConnReq @@ -4234,8 +4232,27 @@ processChatCommand cxt nm = \case case plan of CPContactAddress (CAPContactViaAddress Contact {contactId}) -> processChatCommand cxt nm $ APIConnectContactViaAddress userId incognito contactId + CPGroupLink (GLPOk (Just GroupShortLinkInfo {direct = False}) (Just gld) _) + | ACCL SCMContact ccl <- ccLink -> joinChannelViaRelays ccl gld _ -> processChatCommand cxt nm $ APIConnect userId incognito $ Just ccLink | otherwise = pure $ CRConnectionPlan user ccLink plan + where + joinChannelViaRelays :: CreatedLinkContact -> GroupShortLinkData -> CM ChatResponse + joinChannelViaRelays ccl gld = do + GroupInfo {groupId} <- prepareChannelGroup + processChatCommand cxt nm APIConnectPreparedGroup {groupId, incognito, ownerContact = Nothing, msgContent_ = Nothing} + `catchAllErrors` \e -> do + deletePreparedChannel groupId `catchAllErrors` eToView + throwError e + where + prepareChannelGroup = + processChatCommand cxt nm (APIPrepareGroup userId ccl False gld) >>= \case + CRNewPreparedChat _ (AChat SCTGroup (Chat (GroupChat gInfo _) _ _)) -> pure gInfo + _ -> throwChatError $ CEException "joinChannelViaRelays: unexpected response from APIPrepareGroup" + deletePreparedChannel groupId = do + gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId + deleteGroupConnections user gInfo False + withFastStore' $ \db -> deleteGroup db user gInfo invitationRequestPlan :: User -> ConnReqInvitation -> Maybe ContactShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan invitationRequestPlan user cReq cld ov = do maybe (CPInvitationLink (ILPOk cld ov)) (invitationEntityPlan cld ov) diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d637328925..b667459b6d 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -255,6 +255,8 @@ chatGroupTests = do describe "multiple relays" $ do it "2 relays: should deliver messages to members" testChannels2RelaysDeliver it "should share same incognito profile with all relays" testChannels2RelaysIncognito + it "should connect to channel via /c (CLI)" testConnectChannelCLI + it "should connect to channel via /c incognito (CLI)" testConnectChannelCLIIncognito describe "deliver member profiles via relay" $ do it "late joiner (no prior history) learns sender on first forward" testChannelLateJoinerReceivesProfile it "2 relays: deduplicate member announcement" testChannel2RelaysDeduplicateProfile @@ -8644,6 +8646,61 @@ testSupportPreferenceChannel ps = bob <# "#team (support) alice> yes [>>]" ] +testConnectChannelCLI :: HasCallStack => TestParams -> IO () +testConnectChannelCLI ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> + withNewTestChatOpts ps relayTestOpts "cath" cathProfile $ \cath -> + withNewTestChat ps "dan" danProfile $ \dan -> do + (shortLink, _fullLink) <- prepareChannel2Relays "team" alice bob cath + relayNames <- mapM userName [bob, cath] + mName <- userName dan + mFullName <- showName dan + dan ##> ("/c " <> shortLink) + dan <## "#team: connection started" + concurrentlyN_ $ + [ dan + <### concat + [ [ ConsoleString ("#team: joining the group (connecting to relay " <> rName <> ")..."), + ConsoleString ("#team: you joined the group (connected to relay " <> rName <> ")") + ] + | rName <- relayNames + ] + ] + <> [ do + relay <## (mFullName <> ": accepting request to join group #team...") + relay <## ("#team: " <> mName <> " joined the group") + | relay <- [bob, cath] + ] + <> [alice <### [EndsWith ("introduced " <> mFullName <> " in the channel")]] + +testConnectChannelCLIIncognito :: HasCallStack => TestParams -> IO () +testConnectChannelCLIIncognito ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> + withNewTestChatOpts ps relayTestOpts "cath" cathProfile $ \cath -> + withNewTestChat ps "dan" danProfile $ \dan -> do + (shortLink, _fullLink) <- prepareChannel2Relays "team" alice bob cath + relayNames <- mapM userName [bob, cath] + dan ##> ("/c i " <> shortLink) + danIncognito <- getTermLine dan + dan <## "#team: connection started incognito" + concurrentlyN_ $ + [ dan + <### concat + [ [ ConsoleString ("#team: joining the group (connecting to relay " <> rName <> ")..."), + ConsoleString ("#team: you joined the group (connected to relay " <> rName <> ") incognito as " <> danIncognito) + ] + | rName <- relayNames + ] + ] + <> [ do + relay <## (danIncognito <> ": accepting request to join group #team...") + relay <## ("#team: " <> danIncognito <> " joined the group") + | relay <- [bob, cath] + ] + <> [alice <### [EndsWith ("introduced " <> danIncognito <> " in the channel")]] + testChannels1RelayDeliver :: HasCallStack => TestParams -> IO () testChannels1RelayDeliver ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do