align and simplify core/UI/UXcanvas

This commit is contained in:
Alain Brenzikofer
2026-09-30 20:11:51 +02:00
parent 10c456b057
commit 6bc2f4450e
26 changed files with 634 additions and 460 deletions
+3 -3
View File
@@ -885,7 +885,7 @@ data ChatResponse
| CRInvitation {user :: User, connLinkInvitation :: CreatedLinkInvitation, connection :: PendingContactConnection}
| CRConnectionIncognitoUpdated {user :: User, toConnection :: PendingContactConnection, customUserProfile :: Maybe Profile}
| CRConnectionUserChanged {user :: User, fromConnection :: PendingContactConnection, toConnection :: PendingContactConnection, newUser :: User}
| CRConnectionPlan {user :: User, connLink :: Maybe ACreatedConnLink, planSimplexName :: Maybe SimplexNameInfo, otherSimplexName :: Maybe SimplexNameInfo, connectionPlan :: ConnectionPlan}
| CRConnectionPlan {user :: User, connLink :: Maybe ACreatedConnLink, planSimplexName :: Maybe SimplexNameInfo, otherSimplexName :: Maybe SimplexNameInfo, connectionPlan :: ConnectionPlan, localChats :: [AChatInfo], offerLookup :: Bool}
| CRNewPreparedChat {user :: User, chat :: AChat}
| CRContactUserChanged {user :: User, fromContact :: Contact, newUser :: User, toContact :: Contact}
| CRGroupUserChanged {user :: User, fromGroup :: GroupInfo, newUser :: User, toGroup :: GroupInfo}
@@ -1180,7 +1180,7 @@ data InvitationLinkPlan
deriving (Show)
data ContactAddressPlan
= CAPOk {contactSLinkData_ :: Maybe ContactShortLinkData, ownerVerification :: Maybe OwnerVerification, addressChanged :: Bool, existingChat_ :: Maybe AChatInfo}
= CAPOk {contactSLinkData_ :: Maybe ContactShortLinkData, ownerVerification :: Maybe OwnerVerification, addressChanged :: Bool}
| CAPOwnLink
| CAPConnectingConfirmReconnect
| CAPConnectingProhibit {contact :: Contact}
@@ -1189,7 +1189,7 @@ data ContactAddressPlan
deriving (Show)
data GroupLinkPlan
= GLPOk {groupSLinkInfo_ :: Maybe GroupShortLinkInfo, groupSLinkData_ :: Maybe GroupShortLinkData, ownerVerification :: Maybe OwnerVerification, addressChanged :: Bool, existingChat_ :: Maybe AChatInfo}
= GLPOk {groupSLinkInfo_ :: Maybe GroupShortLinkInfo, groupSLinkData_ :: Maybe GroupShortLinkData, ownerVerification :: Maybe OwnerVerification, addressChanged :: Bool}
| GLPOwnLink {groupInfo :: GroupInfo}
| GLPConnectingConfirmReconnect
| GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo}
+116 -75
View File
@@ -40,7 +40,7 @@ import Data.Foldable (foldr')
import Data.Functor (($>))
import Data.Functor.Identity (Identity (..), runIdentity)
import Data.Int (Int64)
import Data.List (dropWhileEnd, find, foldl', isSuffixOf, partition, sortOn, zipWith4)
import Data.List (dropWhileEnd, find, foldl', isSuffixOf, nubBy, partition, sortOn, zipWith4)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
@@ -2177,7 +2177,8 @@ processChatCommand cxt nm = \case
pure conn'
APIConnectPlan userId (Just ct) resolveMode linkOwnerSig_ -> withUserId userId $ \user -> do
(ccLink, planSimplexName, otherSimplexName, plan) <- connectPlan user ct resolveMode linkOwnerSig_ Nothing
pure $ CRConnectionPlan user ccLink planSimplexName otherSimplexName plan
(localChats, offerLookup) <- planLocalChats user ct plan
pure $ CRConnectionPlan user ccLink planSimplexName otherSimplexName plan localChats offerLookup
APIConnectPlan _ Nothing _ _ -> throwChatError CEInvalidConnReq
APIPrepareContact userId accLink verifiedDomain contactSLinkData -> withUserId userId $ \user -> do
let ContactShortLinkData {profile, message, business} = contactSLinkData
@@ -2192,6 +2193,7 @@ processChatCommand cxt nm = \case
groupProfile = businessGroupProfile profile groupPreferences
gVar <- asks random
(gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing domain_
forM_ domain_ $ nameChatsUpdated user NTContact
hostMember <- maybe (throwCmdError "no host member") pure hostMember_
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart)
let cd = CDGroupRcv gInfo Nothing hostMember
@@ -2205,6 +2207,7 @@ processChatCommand cxt nm = \case
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
ACCL _ (CCLink cReq _) -> do
ct <- withStore $ \db -> createPreparedContact db cxt user profile accLink welcomeSharedMsgId (True <$ domain_)
forM_ domain_ $ nameChatsUpdated user NTContact
void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing Nothing (Just epochStart)
let cd = CDDirectRcv ct
createItem sharedMsgId content = createChatItem user cd False content sharedMsgId Nothing Nothing
@@ -2221,6 +2224,7 @@ processChatCommand cxt nm = \case
domain_ = if verifiedDomain == (claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) then verifiedDomain else Nothing
welcomeSharedMsgId <- forM description $ \_ -> getSharedMsgId
(gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId domain_
forM_ domain_ $ nameChatsUpdated user NTPublicGroup
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart)
let cd = maybe (CDChannelRcv gInfo Nothing) (CDGroupRcv gInfo Nothing) hostMember_
cInfo = GroupChat gInfo Nothing
@@ -2407,7 +2411,7 @@ processChatCommand cxt nm = \case
ACTarget m (CTFullContact cReq) -> con m cReq
ACTarget m (CTInv (CLFull cReq)) -> con m cReq
_ -> throwError e
connectWithPlan user incognito ccLink planSimplexName otherSimplexName plan
connectWithPlan user incognito ccLink planSimplexName otherSimplexName plan =<< planLocalChats user ct plan
Connect _ Nothing -> throwChatError CEInvalidConnReq
APIVerifyContactDomain contactId -> withUser $ \user -> do
ct@Contact {profile = LocalProfile {contactDomain}, preparedContact} <- withFastStore $ \db -> getContact db cxt user contactId
@@ -2415,6 +2419,7 @@ processChatCommand cxt nm = \case
domain <- maybe (throwCmdError "contact has no name to verify") pure contactDomain
(verified, reason) <- verifyEntityDomain user nm NTContact domain connLink_
ct' <- maybe (pure ct) (\v -> withFastStore' $ \db -> setContactDomainVerified db user ct v Nothing) verified
when (verified == Just True) $ nameChatsUpdated user NTContact $ claimDomain domain
pure $ CRContactDomainVerified user ct' reason
APIVerifyGroupDomain groupId -> withUser $ \user -> do
g@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
@@ -2429,6 +2434,7 @@ processChatCommand cxt nm = \case
Left (ChatErrorAgent {agentError = SMP _ (NAME SMP.NOT_FOUND)}) -> pure (False, Just "the name is not registered")
Left e -> throwError e
g' <- withFastStore' $ \db -> setGroupDomainVerified db user g verified Nothing
when verified $ nameChatsUpdated user NTPublicGroup $ claimDomain claim
pure $ CRGroupDomainVerified user g' reason
APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do
ct@Contact {profile = LocalProfile {contactLink}, groupDirectInv} <- withFastStore $ \db -> getContact db cxt user contactId
@@ -2446,8 +2452,8 @@ processChatCommand cxt nm = \case
toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct')
throwError e
ConnectSimplex incognito -> withUser $ \user -> do
plan <- contactRequestPlan user adminContactReq Nothing Nothing `catchAllErrors` const (pure $ CPContactAddress (CAPOk Nothing Nothing False Nothing) Nothing)
connectWithPlan user incognito (Just (ACCL SCMContact (CCLink adminContactReq Nothing))) Nothing Nothing plan
plan <- contactRequestPlan user adminContactReq Nothing Nothing `catchAllErrors` const (pure $ CPContactAddress (CAPOk Nothing Nothing False) Nothing)
connectWithPlan user incognito (Just (ACCL SCMContact (CCLink adminContactReq Nothing))) Nothing Nothing plan ([], False)
DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId Nothing) cdm
ClearContact cName -> withContactName cName $ \chatId -> APIClearChat $ ChatRef CTDirect chatId Nothing
APIListContacts userId -> withUserId userId $ \user ->
@@ -4113,6 +4119,7 @@ processChatCommand cxt nm = \case
gInfo' <- withStore $ \db -> do
g <- updateGroupProfile db user gInfo p'
if domainVerified then liftIO $ setGroupDomainVerified db user g True Nothing else pure g
when domainVerified $ forM_ (publicGroup p' >>= publicGroupAccess >>= groupDomainClaim) $ nameChatsUpdated user NTPublicGroup . claimDomain
msg <- case businessChat of
Just BusinessChatInfo {businessId} -> do
ms <- withStore' $ \db -> getGroupMembers db cxt user gInfo'
@@ -4418,11 +4425,12 @@ processChatCommand cxt nm = \case
pure (Just (ACCL SCMInvitation (CCLink cReq sLnk_)), Nothing, Nothing, plan)
connectPlan user (ACTarget SCMContact ct) resolveMode sig_ nameReg_ = case ct of
CTDomain d -> do
channel_ <- knownGroupPlans $ CTName channelName
contact_ <- knownContactPlans $ CTName contactName
channel_ <- knownGroupPlans user $ CTName channelName
contact_ <- knownContactPlans user $ CTName contactName
let match_ = (channelName,) <$> channel_ <|> (contactName,) <$> contact_
bothLocal = isJust channel_ && isJust contact_
case match_ of
Just (ni, ((l, p), fresh)) | resolveMode == PRMNever || (resolveMode == PRMUnknown && fresh) -> pure (Just l, Just ni, Nothing, p)
Just (ni, ((l, p), fresh)) | resolveMode == PRMNever || (resolveMode == PRMUnknown && fresh && bothLocal) -> pure (Just l, Just ni, Nothing, p)
Nothing | resolveMode == PRMNever -> throwChatError CENotResolvedLocally
_ ->
tryAllErrors (resolveNameRegistration user nm d) >>= \case
@@ -4438,12 +4446,19 @@ processChatCommand cxt nm = \case
NTPublicGroup -> newName contactName contact_
NTContact -> newName channelName channel_
newName ni known_ = case linkOrWarning ni of
Right l' | (knownLinkOf . fst . fst =<< known_) /= Just l' -> Just ni
Right l' | not (any (isLinkOf l' . fst . fst) known_) -> Just ni
_ -> Nothing
contactPlanOr fallback = namePlanWith Nothing contactName `catchAllErrors` \_ -> fallback
unjoinable (_, _, _, p) = case p of
CPGroupLink GLPNoRelays {} _ -> True
CPGroupLink GLPUpdateRequired {} _ -> True
_ -> False
case match_ of
Just (ni, _) -> namePlan ni
Nothing -> case (linkOrWarning channelName, linkOrWarning contactName) of
(Right _, Right _) -> namePlan channelName `catchAllErrors` \e -> (namePlanWith Nothing contactName `catchAllErrors` \_ -> throwError e)
(Right _, Right _) ->
(namePlan channelName >>= \r -> if unjoinable r then contactPlanOr (pure r) else pure r)
`catchAllErrors` \e -> contactPlanOr (throwError e)
(Right _, Left _) -> namePlan channelName
(Left _, Right _) -> namePlan contactName
(Left w, Left _) -> pure (Nothing, Nothing, Nothing, CPNameNotConnectable d w)
@@ -4456,7 +4471,7 @@ processChatCommand cxt nm = \case
pure (Just (ACCL SCMContact $ CCLink cReq Nothing), Nothing, Nothing, plan)
CTShortContact nl ->
(\(l, p) -> (l, simplexName_, Nothing, p)) <$> case ctType of
CCTContact -> knownContactPlans nl' >>= shortLinkPlan confirmKnown resolvedPlan
CCTContact -> knownContactPlans user nl' >>= shortLinkPlan confirmKnown resolvedPlan
where
confirmKnown expiresAt _ r@(_, p) = r <$ when (isJust simplexName_) (setKnownVerified expiresAt p)
resolvedPlan expiresAt l' = do
@@ -4498,10 +4513,12 @@ processChatCommand cxt nm = \case
CTName SimplexNameInfo {nameType = NTContact} -> CCTContact
CTName SimplexNameInfo {nameType = NTPublicGroup} -> CCTChannel
con l' cReq = ACCL SCMContact $ CCLink cReq (Just l')
setKnownVerified expiresAt = \case
CPContactAddress (CAPKnown ct') _ -> void $ withFastStore' $ \db -> setContactDomainVerified db user ct' True expiresAt
CPGroupLink (GLPKnown g _ _ _) _ -> void $ withFastStore' $ \db -> setGroupDomainVerified db user g True expiresAt
_ -> pure ()
setKnownVerified expiresAt p = do
verified <- case p of
CPContactAddress (CAPKnown ct') _ -> True <$ withFastStore' (\db -> setContactDomainVerified db user ct' True expiresAt)
CPGroupLink (GLPKnown g _ _ _) _ -> True <$ withFastStore' (\db -> setGroupDomainVerified db user g True expiresAt)
_ -> pure False
when verified $ forM_ simplexName_ $ \SimplexNameInfo {nameType, nameDomain} -> nameChatsUpdated user nameType nameDomain
shortLinkPlan ::
(Maybe UTCTime -> ShortLinkContact -> (ACreatedConnLink, ConnectionPlan) -> CM (ACreatedConnLink, ConnectionPlan)) ->
(Maybe UTCTime -> ShortLinkContact -> CM (ACreatedConnLink, ConnectionPlan)) ->
@@ -4537,15 +4554,15 @@ processChatCommand cxt nm = \case
Just r@(_, p) | knownChat p -> pure r
_ -> throwError e
linkPlan expiresAt l' = case known_ of
Just r@(l, p)
| knownLinkOf l == Just l' -> confirmKnown expiresAt l' r
Just r@(l, _)
| isLinkOf l' l -> confirmKnown expiresAt l' r
| otherwise ->
(second (setAddressChanged p) <$> resolvedPlan expiresAt l') `catchAllErrors` \case
((\(l'', p) -> maybe r (l'',) $ setAddressChanged p) <$> resolvedPlan expiresAt l') `catchAllErrors` \case
ChatError (CESimplexDomainNotReady _ SDEUnknownDomain) -> pure r
e -> throwError e
Nothing -> resolvedPlan expiresAt l'
groupShortLinkPlan :: CM (Maybe ACreatedConnLink, ConnectionPlan)
groupShortLinkPlan = knownGroupPlans nl' >>= shortLinkPlan confirmKnown resolvedGroupPlan
groupShortLinkPlan = knownGroupPlans user nl' >>= shortLinkPlan confirmKnown resolvedGroupPlan
where
confirmKnown expiresAt l' = \case
(_, CPGroupLink (GLPKnown g _ _ _) _) -> resolveKnownGroup expiresAt l' g
@@ -4578,7 +4595,7 @@ processChatCommand cxt nm = \case
_ -> pure plan0
forM_ planDomain $ \nameDomain ->
let domain_ = (\GroupProfile {publicGroup} -> claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) =<< case plan of
CPGroupLink (GLPOk _ (Just GroupShortLinkData {groupProfile}) _ _ _) _ -> Just groupProfile
CPGroupLink (GLPOk _ (Just GroupShortLinkData {groupProfile}) _ _) _ -> Just groupProfile
CPGroupLink (GLPKnown GroupInfo {groupProfile} _ _ _) _ -> Just groupProfile
CPGroupLink (GLPOwnLink GroupInfo {groupProfile}) _ -> Just groupProfile
CPGroupLink (GLPConnectingProhibit (Just GroupInfo {groupProfile})) _ -> Just groupProfile
@@ -4598,54 +4615,79 @@ processChatCommand cxt nm = \case
_ -> pure (g, False)
pure (con l' cReq, CPGroupLink (GLPKnown g' updated ov (ListDef glOwners)) Nothing)
where
knownContactPlans :: ContactNameOrLink -> CM (Maybe ((ACreatedConnLink, ConnectionPlan), Bool))
knownContactPlans nl' = withFastStore $ \db ->
liftIO (getUserContactLinkViaTarget db user nl') >>= \case
Just UserContactLink {connLinkContact} -> pure $ Just ((ACCL SCMContact connLinkContact, CPContactAddress CAPOwnLink Nothing), False)
Nothing ->
getContactToConnect db cxt user nl' >>= \case
Just (ccl, ct')
| contactDeleted ct' -> pure Nothing
| otherwise -> do
fresh <- liftIO $ resolvedRecently =<< getContactDomainResolution db user ct'
pure $ Just ((ACCL SCMContact ccl, CPContactAddress (CAPKnown ct') Nothing), fresh)
Nothing -> gPlan db =<< getGroupToConnect db cxt user nl'
knownGroupPlans :: ContactNameOrLink -> CM (Maybe ((ACreatedConnLink, ConnectionPlan), Bool))
knownGroupPlans nl' = withFastStore $ \db ->
liftIO (getGroupInfoViaUserTarget db cxt user nl') >>= \case
Just (ccl, g) -> pure $ Just ((ACCL SCMContact ccl, CPGroupLink (GLPOwnLink g) Nothing), False)
Nothing -> gPlan db =<< getGroupToConnect db cxt user nl'
gPlan db = \case
Just (ccl, g) | not (memberRemoved (membership g)) -> do
fresh <- liftIO $ resolvedRecently =<< getGroupDomainResolution db user g
pure $ Just ((ACCL SCMContact ccl, CPGroupLink (GLPKnown g False Nothing (ListDef [])) Nothing), fresh)
_ -> pure Nothing
resolvedRecently :: Maybe (UTCTime, Maybe UTCTime) -> IO Bool
resolvedRecently = \case
Just (resolvedAt, expiresAt_) -> do
now <- getCurrentTime
pure $ diffUTCTime now resolvedAt < nominalDay && maybe True (now <) expiresAt_
Nothing -> pure False
knownChat = \case
CPContactAddress (CAPKnown _) _ -> True
CPGroupLink GLPKnown {} _ -> True
_ -> False
knownLinkOf :: ACreatedConnLink -> Maybe ShortLinkContact
knownLinkOf = \case
ACCL SCMContact (CCLink _ sl_) -> sl_
isLinkOf :: ShortLinkContact -> ACreatedConnLink -> Bool
isLinkOf l' = \case
ACCL SCMContact (CCLink _ (Just sl)) -> sameShortLinkContact sl l'
_ -> False
knownContactPlans :: User -> ContactNameOrLink -> CM (Maybe ((ACreatedConnLink, ConnectionPlan), Bool))
knownContactPlans user nl' = withFastStore $ \db ->
liftIO (getUserContactLinkViaTarget db user nl') >>= \case
Just UserContactLink {connLinkContact} -> pure $ Just ((ACCL SCMContact connLinkContact, CPContactAddress CAPOwnLink Nothing), False)
Nothing ->
getContactToConnect db cxt user nl' >>= \case
Just (ccl, ct')
| contactDeleted ct' -> pure Nothing
| otherwise -> do
fresh <- liftIO $ resolvedRecently =<< getContactDomainResolution db user ct'
pure $ Just ((ACCL SCMContact ccl, CPContactAddress (CAPKnown ct') Nothing), fresh)
Nothing -> gPlan user db =<< getGroupToConnect db cxt user nl'
knownGroupPlans :: User -> ContactNameOrLink -> CM (Maybe ((ACreatedConnLink, ConnectionPlan), Bool))
knownGroupPlans user nl' = withFastStore $ \db ->
liftIO (getGroupInfoViaUserTarget db cxt user nl') >>= \case
Just (ccl, g) -> pure $ Just ((ACCL SCMContact ccl, CPGroupLink (GLPOwnLink g) Nothing), False)
Nothing -> gPlan user db =<< getGroupToConnect db cxt user nl'
gPlan user db = \case
Just (ccl, g) | not (memberRemoved (membership g)) -> do
fresh <- liftIO $ resolvedRecently =<< getGroupDomainResolution db user g
pure $ Just ((ACCL SCMContact ccl, CPGroupLink (GLPKnown g False Nothing (ListDef [])) Nothing), fresh)
_ -> pure Nothing
resolvedRecently :: Maybe (UTCTime, Maybe UTCTime) -> IO Bool
resolvedRecently = \case
Just (resolvedAt, expiresAt_) -> do
now <- getCurrentTime
pure $ diffUTCTime now resolvedAt < nominalDay && maybe True (now <) expiresAt_
Nothing -> pure False
-- the chats a plan is about, the planned one first, and whether looking the name up can show more than them
planLocalChats :: User -> AConnectTarget -> ConnectionPlan -> CM ([AChatInfo], Bool)
planLocalChats user ct plan = do
found <- nameLookups
let chats = nubBy (\(AChatInfo _ c) (AChatInfo _ c') -> chatInfoToRef c == chatInfoToRef c') $ mapMaybe planChat $ plan : map (snd . fst) (catMaybes found)
pure (chats, not $ all (maybe False snd) found)
where
nameLookups :: CM [Maybe ((ACreatedConnLink, ConnectionPlan), Bool)]
nameLookups = case ct of
ACTarget SCMContact (CTDomain d) -> sequence [knownGroupPlans user $ CTName $ SimplexNameInfo NTPublicGroup d, knownContactPlans user $ CTName $ SimplexNameInfo NTContact d]
ACTarget SCMContact (CTShortContact nl@(CTName SimplexNameInfo {nameType = NTContact})) -> (: []) <$> knownContactPlans user nl
ACTarget SCMContact (CTShortContact nl@(CTName SimplexNameInfo {nameType = NTPublicGroup})) -> (: []) <$> knownGroupPlans user nl
_ -> pure []
planChat = \case
CPInvitationLink (ILPConnecting (Just ct')) -> directChat ct'
CPInvitationLink (ILPKnown ct') -> directChat ct'
CPContactAddress (CAPConnectingProhibit ct') _ -> directChat ct'
CPContactAddress (CAPKnown ct') _ -> directChat ct'
CPContactAddress (CAPContactViaAddress ct') _ -> directChat ct'
CPGroupLink (GLPOwnLink g) _ -> groupChat g
CPGroupLink (GLPConnectingProhibit (Just g)) _ -> groupChat g
CPGroupLink (GLPKnown g _ _ _) _ -> groupChat g
_ -> Nothing
connectWithPlan :: User -> IncognitoEnabled -> Maybe ACreatedConnLink -> Maybe SimplexNameInfo -> Maybe SimplexNameInfo -> ConnectionPlan -> CM ChatResponse
connectWithPlan user@User {userId} incognito ccLink_ planSimplexName otherSimplexName plan
directChat = Just . AChatInfo SCTDirect . DirectChat
groupChat g = Just $ AChatInfo SCTGroup $ GroupChat g Nothing
connectWithPlan :: User -> IncognitoEnabled -> Maybe ACreatedConnLink -> Maybe SimplexNameInfo -> Maybe SimplexNameInfo -> ConnectionPlan -> ([AChatInfo], Bool) -> CM ChatResponse
connectWithPlan user@User {userId} incognito ccLink_ planSimplexName otherSimplexName plan (localChats, offerLookup)
| Just ccLink <- ccLink_, connectionPlanProceed plan = do
case plan of CPError e -> eToView e; _ -> pure ()
case plan of
CPContactAddress (CAPContactViaAddress Contact {contactId}) _ ->
processChatCommand cxt nm $ APIConnectContactViaAddress userId incognito contactId
CPContactAddress (CAPOk (Just sld) _ _ _) _ | isJust vName -> connectContactViaName ccLink sld
CPGroupLink (GLPOk (Just GroupShortLinkInfo {direct = False}) (Just gld) _ _ _) _
CPContactAddress (CAPOk (Just sld) _ _) _ | isJust vName -> connectContactViaName ccLink sld
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_ planSimplexName otherSimplexName plan
| otherwise = pure $ CRConnectionPlan user ccLink_ planSimplexName otherSimplexName plan localChats offerLookup
where
vName = nameDomain <$> planSimplexName
joinChannelViaRelays :: CreatedLinkContact -> GroupShortLinkData -> CM ChatResponse
@@ -4709,13 +4751,13 @@ processChatCommand cxt nm = \case
Nothing ->
withFastStore' (\db -> getContactWithoutConnViaAddress db cxt user cReqSchemas) >>= \case
Just ct | not (contactDeleted ct) -> plan $ CAPContactViaAddress ct
_ -> plan $ CAPOk cld ov False Nothing
_ -> plan $ CAPOk cld ov False
Just (RcvDirectMsgConnection Connection {connStatus} Nothing)
| connStatus == ConnPrepared -> plan $ CAPOk cld ov False Nothing
| connStatus == ConnPrepared -> plan $ CAPOk cld ov False
| otherwise -> plan CAPConnectingConfirmReconnect
Just (RcvDirectMsgConnection _ (Just ct))
| not (contactReady ct) && contactActive ct -> plan $ CAPConnectingProhibit ct
| contactDeleted ct -> plan $ CAPOk cld ov False Nothing
| contactDeleted ct -> plan $ CAPOk cld ov False
| otherwise -> plan $ CAPKnown ct
-- TODO [short links] RcvGroupMsgConnection branch is deprecated? (old group link protocol?)
Just (RcvGroupMsgConnection _ gInfo _) -> groupPlan gInfo Nothing Nothing Nothing []
@@ -4731,12 +4773,12 @@ processChatCommand cxt nm = \case
connEnt_ <- withFastStore' $ \db -> getContactConnEntityByConnReqHash db cxt user cReqHashes
gInfo_ <- withFastStore' $ \db -> getGroupInfoByGroupLinkHash db cxt user cReqHashes
case (gInfo_, connEnt_) of
(Nothing, Nothing) -> plan $ GLPOk linkInfo gld ov False Nothing
(Nothing, Nothing) -> plan $ GLPOk linkInfo gld ov False
-- TODO [short links] RcvDirectMsgConnection branches are deprecated? (old group link protocol?)
(Nothing, Just (RcvDirectMsgConnection _conn Nothing)) -> plan $ GLPConnectingConfirmReconnect
(Nothing, Just (RcvDirectMsgConnection _ (Just ct)))
| not (contactReady ct) && contactActive ct -> plan $ GLPConnectingProhibit gInfo_
| otherwise -> plan $ GLPOk linkInfo gld ov False Nothing
| otherwise -> plan $ GLPOk linkInfo gld ov False
(Nothing, Just _) -> throwCmdError "found connection entity is not RcvDirectMsgConnection"
(Just gInfo, _) -> groupPlan gInfo linkInfo gld ov glOwners
groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> [GroupLinkOwner] -> CM ConnectionPlan
@@ -4745,7 +4787,7 @@ processChatCommand cxt nm = \case
| not (memberActive membership) && not (memberRemoved membership) =
plan $ GLPConnectingProhibit $ Just gInfo
| memberActive membership = plan $ GLPKnown gInfo False ov (ListDef glOwners)
| otherwise = plan $ GLPOk linkInfo gld ov False Nothing
| otherwise = plan $ GLPOk linkInfo gld ov False
where
plan p = pure $ CPGroupLink p Nothing
contactCReqSchemas :: ConnReqContact -> (ConnReqContact, ConnReqContact)
@@ -5158,7 +5200,7 @@ nameExpiresAt = \case
nameLinkOrWarning :: SystemSeconds -> SimplexNameInfo -> NameRegistration -> Either NameWarning ShortLinkContact
nameLinkOrWarning now SimplexNameInfo {nameType, nameDomain = SimplexDomain {domain}} = \case
NRRegistered {expires = Just expires, graceUntil}
| expires < now -> Left $ NWExpired (roundedToUTCTime expires) (roundedToUTCTime <$> graceUntil)
| expires < now -> Left $ NWExpired (roundedToUTCTime expires) (roundedToUTCTime <$> mfilter (now <=) graceUntil)
NRRegistered {nameRecord = NameRecord {nrSimplexContact, nrSimplexChannel}} ->
maybe (Left NWNoValidLink) Right $ case nameType of
NTContact -> firstNameLink CCTContact nrSimplexContact
@@ -5192,18 +5234,17 @@ setNameWarning w = \case
NWReservedForCommunity -> Just w
_ -> Nothing
setAddressChanged :: ConnectionPlan -> ConnectionPlan -> ConnectionPlan
setAddressChanged known = \case
CPContactAddress (CAPOk cld ov _ _) w_ -> CPContactAddress (CAPOk cld ov True existingChat_) w_
CPGroupLink (GLPOk li gld ov _ _) w_ -> CPGroupLink (GLPOk li gld ov True existingChat_) w_
p -> p
where
existingChat_ = case known of
CPContactAddress (CAPKnown ct) _ -> Just $ AChatInfo SCTDirect $ DirectChat ct
CPGroupLink (GLPKnown g _ _ _) _ -> groupChat g
CPGroupLink (GLPOwnLink g) _ -> groupChat g
_ -> Nothing
groupChat g = Just $ AChatInfo SCTGroup $ GroupChat g Nothing
setAddressChanged :: ConnectionPlan -> Maybe ConnectionPlan
setAddressChanged = \case
CPContactAddress (CAPOk cld ov _) w_ -> Just $ CPContactAddress (CAPOk cld ov True) w_
CPGroupLink (GLPOk li gld ov _) w_ -> Just $ CPGroupLink (GLPOk li gld ov True) w_
CPContactAddress CAPConnectingConfirmReconnect _ -> Nothing
CPContactAddress CAPConnectingProhibit {} _ -> Nothing
CPGroupLink GLPConnectingConfirmReconnect _ -> Nothing
CPGroupLink GLPConnectingProhibit {} _ -> Nothing
CPGroupLink GLPNoRelays {} _ -> Nothing
CPGroupLink GLPUpdateRequired {} _ -> Nothing
p -> Just p
verifyEntityDomain :: User -> NetworkRequestMode -> SimplexNameType -> SimplexDomainClaim -> Maybe AConnShortLink -> CM (Maybe Bool, Maybe Text)
verifyEntityDomain user nm nameType SimplexDomainClaim {domain = StrJSON domain, proof = proof_} connLink_ = case (proof_, connLink_) of
+16 -4
View File
@@ -1619,14 +1619,16 @@ updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> Maybe Simp
updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} resolvedDomain_ expiresAt
| profileChanged || countChanged || verifyResolved = do
cxt <- chatStoreCxt
withStore $ \db -> do
g'' <- withStore $ \db -> do
g <- if profileChanged then updateGroupProfile db user gInfo groupProfile else pure gInfo
g' <- case publicGroupData of
Just PublicGroupData {publicMemberCount} | countChanged ->
setPublicMemberCount db cxt user g publicMemberCount
_ -> pure g
g'' <- if verifyResolved then liftIO $ setGroupDomainVerified db user g' True expiresAt else pure g'
pure (g'', profileChanged)
if verifyResolved then liftIO $ setGroupDomainVerified db user g' True expiresAt else pure g'
toView $ CEvtGroupUpdated user gInfo g'' Nothing Nothing
when verifyResolved $ forM_ newClaim $ nameChatsUpdated user NTPublicGroup
pure (g'', profileChanged)
| otherwise = pure (gInfo, False)
where
profileChanged = p /= groupProfile
@@ -1641,15 +1643,25 @@ updateContactFromLinkData :: User -> Contact -> Profile -> Maybe UTCTime -> CM C
updateContactFromLinkData user ct@Contact {profile = profile@LocalProfile {contactDomain = prevClaim, contactDomainVerified}} linkProfile@Profile {contactDomain = newClaim} expiresAt
| profileChanged || verifyChanged = do
cxt <- chatStoreCxt
withFastStore $ \db -> do
ct'' <- withFastStore $ \db -> do
ct' <- updateContactProfile db cxt user ct linkProfile
if verifyChanged then liftIO $ setContactDomainVerified db user ct' True expiresAt else pure ct'
toView $ CEvtContactUpdated user ct ct''
when verifyChanged $ forM_ newClaim $ nameChatsUpdated user NTContact . claimDomain
pure ct''
| otherwise = pure ct
where
profileChanged = fromLocalProfile profile /= linkProfile
claimChanged = (claimDomain <$> prevClaim) /= (claimDomain <$> newClaim)
verifyChanged = contactDomainVerified /= Just True || claimChanged
nameChatsUpdated :: User -> SimplexNameType -> SimplexDomain -> CM ()
nameChatsUpdated user nameType domain = do
cxt <- chatStoreCxt
(cts, gs) <- withStore' $ \db -> getNameChats db cxt user nameType domain
forM_ cts $ \ct -> toView $ CEvtContactUpdated user ct ct
forM_ gs $ \g -> toView $ CEvtGroupUpdated user g g Nothing Nothing
-- TODO [relays] owner: set owners on updating link data (multi-owner)
groupLinkData :: GroupInfoKeys -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData)
groupLinkData (GIK gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}, membership = GroupMember {memberId}} gks) GroupLink {groupLinkId} groupRelays =
+15
View File
@@ -47,6 +47,7 @@ module Simplex.Chat.Store.Groups
getGroupInfoByGroupLinkHash,
updateGroupProfile,
setGroupDomainVerified,
getNameChats,
getGroupDomainResolution,
updateGroupPreferences,
updateGroupProfileFromMember,
@@ -2755,6 +2756,20 @@ setGroupDomainVerified db User {userId} g@GroupInfo {groupId, businessChat} veri
(BI verified, currentTs, expiresAt, userId, groupId)
pure g {groupDomainVerified = Just verified}
getNameChats :: DB.Connection -> StoreCxt -> User -> SimplexNameType -> SimplexDomain -> IO ([Contact], [GroupInfo])
getNameChats db cxt user@User {userId} nameType domain = do
cts <- case nameType of
NTContact -> ids "SELECT ct.contact_id FROM contacts ct JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id WHERE ct.user_id = ? AND cp.contact_domain = ? AND ct.deleted = 0" >>= fmap rights . mapM (runExceptT . getContact db cxt user)
NTPublicGroup -> pure []
gs <- ids (groupsQuery <> businessCond) >>= fmap rights . mapM (runExceptT . getGroupInfo db cxt user)
pure (cts, gs)
where
ids q = map fromOnly <$> DB.query db q (userId, domain)
groupsQuery = "SELECT g.group_id FROM groups g JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE g.user_id = ? AND gp.group_domain = ?"
businessCond = case nameType of
NTContact -> " AND g.business_chat IS NOT NULL"
NTPublicGroup -> " AND g.business_chat IS NULL"
getGroupDomainResolution :: DB.Connection -> User -> GroupInfo -> IO (Maybe (UTCTime, Maybe UTCTime))
getGroupDomainResolution db User {userId} GroupInfo {groupId} =
maybeFirstRow id $
+3 -3
View File
@@ -217,7 +217,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te
CRInvitation u ccLink _ -> ttyUser u $ viewConnReqInvitation showFullLinks ccLink
CRConnectionIncognitoUpdated u c customUserProfile -> ttyUser u $ viewConnectionIncognitoUpdated c customUserProfile testView
CRConnectionUserChanged u c c' nu -> ttyUser u $ viewConnectionUserChanged showFullLinks u c nu c'
CRConnectionPlan u connLink planSimplexName otherSimplexName connectionPlan -> ttyUser u $ viewConnectionPlan cfg connLink connectionPlan <> otherSimplexNameNote otherSimplexName <> viewNameWarning planSimplexName connectionPlan
CRConnectionPlan u connLink planSimplexName otherSimplexName connectionPlan _ _ -> ttyUser u $ viewConnectionPlan cfg connLink connectionPlan <> otherSimplexNameNote otherSimplexName <> viewNameWarning planSimplexName connectionPlan
CRNewPreparedChat u (AChat _ (Chat cInfo _ _)) -> ttyUser u $ case cInfo of
DirectChat ct -> [ttyContact' ct <> ": contact is prepared"]
GroupChat g _ -> [ttyGroup' g <> ": group is prepared"]
@@ -2278,7 +2278,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
| business -> ("business address: " <>)
_ -> ("invitation link: " <>)
CPContactAddress cap _ -> case cap of
CAPOk contactSLinkData ov addressChanged _ -> [addrOrBiz contactSLinkData ("ok to connect" <> (if addressChanged then ", address changed" else ""))] <> viewSigVerification ov <> [viewJSON contactSLinkData | testView]
CAPOk contactSLinkData ov addressChanged -> [addrOrBiz contactSLinkData ("ok to connect" <> (if addressChanged then ", address changed" else ""))] <> viewSigVerification ov <> [viewJSON contactSLinkData | testView]
CAPOwnLink -> [ctAddr "own address"]
CAPConnectingConfirmReconnect -> [ctAddr "connecting, allowed to reconnect"]
CAPConnectingProhibit ct -> [ctAddr ("connecting to contact " <> ttyContact' ct)]
@@ -2296,7 +2296,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
| business -> ("business address: " <>)
_ -> ("contact address: " <>)
CPGroupLink glp _ -> case glp of
GLPOk groupSLinkInfo_ groupSLinkData ov addressChanged _ ->
GLPOk groupSLinkInfo_ groupSLinkData ov addressChanged ->
let direct = maybe True (\(GroupShortLinkInfo {direct = d}) -> d) groupSLinkInfo_
in [grpLink $ (if direct then "ok to connect directly" else "ok to connect via relays") <> (if addressChanged then ", address changed" else "")]
<> viewSigVerification ov