mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 06:32:59 +00:00
core: alerts with information about registering names (#7653)
* minimal api change * embed status. reduce * drop fake owner change * no-valid-link fix * add UX flow for name lookups * diagram edits * corrections * update name lookup API * manual renaming * implement core + cli * implement UI * fix * build iOS app blindly * shrink diff * fix review comments * review iterations * opuse 5.5 /goal iterations * fixes EP review * fixes * fixes * short goal iterations * refactor core-UI API to NameWarning with logic in core * diff cosmetics * simplify and normalize diagram * add excalidraw normalizer script and test git hook * cosmetics * commit wireframe collab doc to repo as README * review round on UX coverage * iterate on /goal * iterate on /goal * add tests * iterate on /goal * fix freshness bug with local chats: When core marks a chat verified for a name, it now first un-verifies the user's other chats of the same kind: - a contact name covers contacts and business chats; - a channel name covers channels. * align app divergences * docs fixes * iterations on /goal * align excalidraw * align and simplify core/UI/UXcanvas * 27 rounds of /goal * review * core: simplify connect plan with name lookup * simplify * cache names in a separate table * cache names resolution * wip * all * fix * remove cache * update plans * simplify * bot types * change types * simplify * update bot types * remove review * update view, tests * simplify "unverify" * update query plan * changes * simplify * test helpers * simpler * update query plans * fix * query plans * query plans * fixes * query plans --------- Co-authored-by: Alain Brenzikofer <alain@encointer.org> Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
39 files changed
+2751
-385
No files matched your search
@@ -93,7 +93,7 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), USDCents, XFTPServer)
|
||||
import Simplex.Messaging.Session (SessionVar)
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import Simplex.Messaging.Transport (TLS, TransportPeer (..), simplexMQVersion)
|
||||
@@ -712,8 +712,8 @@ data ChatCommand
|
||||
deriving (Show)
|
||||
|
||||
data PlanResolveMode
|
||||
= PRMAllGroups -- resolve all known groups and all unknown chats
|
||||
| PRMUnknown -- only resolve if chat is unknown (default)
|
||||
= PRMAllGroups -- resolve names, links of known groups and of unknown chats
|
||||
| PRMUnknown -- resolve names, and links of unknown chats (default)
|
||||
| PRMNever -- do not resolve links and names, only do local search
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -987,6 +987,7 @@ data ChatEvent
|
||||
| CEvtGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember}
|
||||
| CEvtBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact}
|
||||
| CEvtContactUpdated {user :: User, fromContact :: Contact, toContact :: Contact}
|
||||
| CEvtNameMoved {user :: User, contactIds :: [ContactId], groupIds :: [GroupId]}
|
||||
| CEvtGroupMemberUpdated {user :: User, groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember}
|
||||
| CEvtContactDeletedByContact {user :: User, contact :: Contact}
|
||||
| CEvtReceivedContactRequest {user :: User, contactRequest :: UserContactRequest, chat_ :: Maybe AChat}
|
||||
@@ -1156,11 +1157,26 @@ data ChatDeleteMode
|
||||
|
||||
data ConnectionPlan
|
||||
= CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan}
|
||||
| CPContactAddress {contactAddressPlan :: ContactAddressPlan}
|
||||
| CPGroupLink {groupLinkPlan :: GroupLinkPlan}
|
||||
| CPContactAddress {contactAddressPlan :: ContactAddressPlan, nameChange :: Maybe NameChange}
|
||||
| CPGroupLink {groupLinkPlan :: GroupLinkPlan, nameChange :: Maybe NameChange}
|
||||
| CPError {chatError :: ChatError}
|
||||
deriving (Show)
|
||||
|
||||
data NameChange
|
||||
= NCLapsed {nameWarning :: NameWarning}
|
||||
| NCMoved {knownChat :: AChatInfo}
|
||||
deriving (Show)
|
||||
|
||||
data NameWarning
|
||||
= NWExpired {expiredAt :: UTCTime, graceUntil :: Maybe UTCTime}
|
||||
| NWAvailable {price :: NamePrice}
|
||||
| NWReservedForCommunity
|
||||
| NWNotRegistered
|
||||
deriving (Eq, Show)
|
||||
|
||||
data NamePrice = NamePrice {amount :: USDCents, years :: Int}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data InvitationLinkPlan
|
||||
= ILPOk {contactSLinkData_ :: Maybe ContactShortLinkData, ownerVerification :: Maybe OwnerVerification}
|
||||
| ILPOwnLink
|
||||
@@ -1219,13 +1235,15 @@ connectionPlanProceed = \case
|
||||
ILPOk {} -> True
|
||||
ILPOwnLink -> True
|
||||
_ -> False
|
||||
CPContactAddress cap -> case cap of
|
||||
CPContactAddress _ (Just NCLapsed {}) -> False
|
||||
CPContactAddress cap _ -> case cap of
|
||||
CAPOk {} -> True
|
||||
CAPOwnLink -> True
|
||||
CAPConnectingConfirmReconnect -> True
|
||||
CAPContactViaAddress _ -> True
|
||||
_ -> False
|
||||
CPGroupLink glp -> case glp of
|
||||
CPGroupLink _ (Just NCLapsed {}) -> False
|
||||
CPGroupLink glp _ -> case glp of
|
||||
GLPOk {} -> True
|
||||
GLPOwnLink _ -> True
|
||||
GLPConnectingConfirmReconnect -> True
|
||||
@@ -1234,6 +1252,12 @@ connectionPlanProceed = \case
|
||||
_ -> False
|
||||
CPError _ -> True
|
||||
|
||||
connectionPlanOwnLink :: ConnectionPlan -> Bool
|
||||
connectionPlanOwnLink = \case
|
||||
CPContactAddress CAPOwnLink _ -> True
|
||||
CPGroupLink GLPOwnLink {} _ -> True
|
||||
_ -> False
|
||||
|
||||
data ForwardConfirmation
|
||||
= FCFilesNotAccepted {fileIds :: [FileTransferId]}
|
||||
| FCFilesInProgress {filesCount :: Int}
|
||||
@@ -1482,6 +1506,7 @@ data ChatError
|
||||
data SimplexDomainError
|
||||
= SDENoValidLink -- the name's record has no usable contact/channel link
|
||||
| SDEUnknownDomain -- the resolved link's profile has no name, or a different name
|
||||
| SDENameWarning {nameWarning :: NameWarning}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data BadgeRedeemError
|
||||
@@ -1849,6 +1874,10 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''NamePrice)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "NW") ''NameWarning)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "SDE") ''SimplexDomainError)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "BRE") ''BadgeRedeemError)
|
||||
@@ -1865,6 +1894,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "NC") ''NameChange)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''AppFilePathsConfig)
|
||||
|
||||
@@ -120,9 +120,10 @@ import Simplex.Messaging.Crypto.Ratchet (E2ERatchetParamsUri (..), InitialKeys (
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (base64P)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (NAME), MsgFlags (..), NameRecord (..), NameRegistration (..), NameResponse (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (NAME), MsgFlags (..), NamePricing (..), NameRecord (..), NameRegistration (..), NameReservedReason (..), NameResponse (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), USDCents (..), UserProtocol, userProtocol)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import Simplex.Messaging.SystemTime (SystemSeconds, getSystemSeconds, roundedToUTCTime)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport.Client (defaultSocksProxyWithAuth)
|
||||
import Simplex.Messaging.Util
|
||||
@@ -1963,7 +1964,7 @@ processChatCommand cxt nm = \case
|
||||
(_, cData@(ContactLinkData _ UserContactData {relays = currentRelayLinks}), _) <- getShortLinkConnReq' nm user sLnk
|
||||
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
|
||||
gInfo' <- case groupSLinkData_ of
|
||||
Just sLinkData -> fst <$> updateGroupFromLinkData user gInfo sLinkData Nothing
|
||||
Just sLinkData -> (\(g, _, _) -> g) <$> updateGroupFromLinkData user gInfo sLinkData Nothing
|
||||
_ -> pure gInfo
|
||||
when (memberRole' (membership gInfo) /= GROwner && memberCurrent (membership gInfo)) $
|
||||
withGroupLock "syncSubscriberRelays" groupId $
|
||||
@@ -2186,7 +2187,8 @@ processChatCommand cxt nm = \case
|
||||
pure $ CRConnectionPlan user ccLink planSimplexName otherSimplexName plan
|
||||
APIConnectPlan _ Nothing _ _ -> throwChatError CEInvalidConnReq
|
||||
APIPrepareContact userId accLink verifiedDomain contactSLinkData -> withUserId userId $ \user -> do
|
||||
let ContactShortLinkData {profile, message, business} = contactSLinkData
|
||||
let ContactShortLinkData {profile = profile@Profile {contactDomain}, message, business} = contactSLinkData
|
||||
domain_ = if verifiedDomain == (claimDomain <$> contactDomain) then verifiedDomain else Nothing
|
||||
welcomeSharedMsgId <- forM message $ \_ -> getSharedMsgId
|
||||
case accLink of
|
||||
ACCL SCMContact ccLink
|
||||
@@ -2195,7 +2197,10 @@ processChatCommand cxt nm = \case
|
||||
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
|
||||
groupProfile = businessGroupProfile profile groupPreferences
|
||||
gVar <- asks random
|
||||
(gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing verifiedDomain
|
||||
(gInfo, hostMember_, moved) <- withStore $ \db -> do
|
||||
(g, hm_) <- createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing domain_
|
||||
(g, hm_,) <$> if isJust domain_ then liftIO (setOtherNameChatsMoved db user (GroupChat g Nothing)) else pure ([], [])
|
||||
nameMovedToView user moved
|
||||
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
|
||||
@@ -2208,7 +2213,10 @@ processChatCommand cxt nm = \case
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
|
||||
ACCL _ (CCLink cReq sLnk) -> do
|
||||
ct <- withStore $ \db -> createPreparedContact db cxt user (linkPresHeader <$> sLnk) profile accLink welcomeSharedMsgId (True <$ verifiedDomain)
|
||||
(ct, moved) <- withStore $ \db -> do
|
||||
ct <- createPreparedContact db cxt user (linkPresHeader <$> sLnk) profile accLink welcomeSharedMsgId (True <$ domain_)
|
||||
(ct,) <$> if isJust domain_ then liftIO (setOtherNameChatsMoved db user (DirectChat ct)) else pure ([], [])
|
||||
nameMovedToView user moved
|
||||
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,9 +2229,10 @@ processChatCommand cxt nm = \case
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTDirect chat
|
||||
APIPrepareGroup userId ccLink direct verifiedDomain groupSLinkData -> withUserId userId $ \user -> do
|
||||
let GroupShortLinkData {groupProfile = GroupProfile {description}} = groupSLinkData
|
||||
let GroupShortLinkData {groupProfile = gp@GroupProfile {description}} = groupSLinkData
|
||||
domain_ = if verifiedDomain == groupClaim gp then verifiedDomain else Nothing
|
||||
welcomeSharedMsgId <- forM description $ \_ -> getSharedMsgId
|
||||
(gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId verifiedDomain
|
||||
(gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId domain_
|
||||
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
|
||||
@@ -2238,7 +2247,10 @@ processChatCommand cxt nm = \case
|
||||
when (isNothing preparedContact) $ throwCmdError "contact doesn't have link to connect"
|
||||
when (isJust $ contactConn ct) $ throwCmdError "contact already has connection"
|
||||
newUser <- privateGetUser newUserId
|
||||
ct' <- withFastStore $ \db -> updatePreparedContactUser db cxt user ct newUser
|
||||
(ct', moved) <- withFastStore $ \db -> do
|
||||
ct'@Contact {profile = LocalProfile {contactDomainVerified}} <- updatePreparedContactUser db cxt user ct newUser
|
||||
(ct',) <$> if contactDomainVerified == Just DVVerified then liftIO (setOtherNameChatsMoved db newUser (DirectChat ct')) else pure ([], [])
|
||||
nameMovedToView newUser moved
|
||||
-- create changed feature items (new user may have different preferences)
|
||||
lift $ createContactChangedFeatureItems user ct ct'
|
||||
pure $ CRContactUserChanged user ct newUser ct'
|
||||
@@ -2253,7 +2265,10 @@ processChatCommand cxt nm = \case
|
||||
when (isJust $ memberConn hostMember) $ throwCmdError "host member already has connection"
|
||||
pure $ Just hostMember
|
||||
newUser <- privateGetUser newUserId
|
||||
gInfo' <- withFastStore $ \db -> updatePreparedGroupUser db cxt user gInfo hostMember_ newUser
|
||||
(gInfo', moved) <- withFastStore $ \db -> do
|
||||
gInfo'@GroupInfo {groupDomainVerified} <- updatePreparedGroupUser db cxt user gInfo hostMember_ newUser
|
||||
(gInfo',) <$> if groupDomainVerified == Just DVVerified then liftIO (setOtherNameChatsMoved db newUser (GroupChat gInfo' Nothing)) else pure ([], [])
|
||||
nameMovedToView newUser moved
|
||||
pure $ CRGroupUserChanged user gInfo newUser gInfo'
|
||||
APIConnectPreparedContact contactId incognito msgContent_ -> withUser $ \user -> do
|
||||
ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db cxt user contactId
|
||||
@@ -2417,7 +2432,12 @@ processChatCommand cxt nm = \case
|
||||
let connLink_ = preparedContact >>= \PreparedContact {connLinkToConnect = ACCL m (CCLink _ sLnk_)} -> ACSL m <$> sLnk_
|
||||
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) verified
|
||||
(ct', moved) <- case verified of
|
||||
Just v -> withFastStore' $ \db -> do
|
||||
ct' <- setContactDomainVerified db user ct v
|
||||
(ct',) <$> if v then setOtherNameChatsMoved db user (DirectChat ct') else pure ([], [])
|
||||
Nothing -> pure (ct, ([], []))
|
||||
nameMovedToView user moved
|
||||
pure $ CRContactDomainVerified user ct' reason
|
||||
APIVerifyGroupDomain groupId -> withUser $ \user -> do
|
||||
g@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
|
||||
@@ -2431,7 +2451,10 @@ processChatCommand cxt nm = \case
|
||||
| otherwise -> pure (False, Just "the name does not resolve to the link in the group profile")
|
||||
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
|
||||
(g', moved) <- withFastStore' $ \db -> do
|
||||
g' <- setGroupDomainVerified db user g verified
|
||||
(g',) <$> if verified then setOtherNameChatsMoved db user (GroupChat g' Nothing) else pure ([], [])
|
||||
nameMovedToView user moved
|
||||
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
|
||||
@@ -2449,7 +2472,7 @@ 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))
|
||||
plan <- contactRequestPlan user adminContactReq Nothing Nothing `catchAllErrors` const (pure $ CPContactAddress (CAPOk Nothing Nothing) Nothing)
|
||||
connectWithPlan user incognito (ACCL SCMContact (CCLink adminContactReq Nothing)) Nothing Nothing plan
|
||||
DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId Nothing) cdm
|
||||
ClearContact cName -> withContactName cName $ \chatId -> APIClearChat $ ChatRef CTDirect chatId Nothing
|
||||
@@ -4169,9 +4192,14 @@ processChatCommand cxt nm = \case
|
||||
checkGroupProfileSize p'
|
||||
when (useRelays' gInfo && isJust (ma' >>= review)) $ throwCmdError "Admission review is not supported in channels"
|
||||
-- updateGroupProfile clears domain verification; re-set it when the caller already re-resolved the name
|
||||
gInfo' <- withStore $ \db -> do
|
||||
(gInfo', moved) <- withStore $ \db -> do
|
||||
g <- updateGroupProfile db user gInfo p'
|
||||
if domainVerified then liftIO $ setGroupDomainVerified db user g True else pure g
|
||||
if domainVerified
|
||||
then liftIO $ do
|
||||
g' <- setGroupDomainVerified db user g True
|
||||
(g',) <$> setOtherNameChatsMoved db user (GroupChat g' Nothing)
|
||||
else pure (g, ([], []))
|
||||
nameMovedToView user moved
|
||||
msg <- case businessChat of
|
||||
Just BusinessChatInfo {businessId} -> do
|
||||
ms <- withStore' $ \db -> getGroupMembers db cxt user gInfo'
|
||||
@@ -4502,7 +4530,7 @@ processChatCommand cxt nm = \case
|
||||
-- local search only: look up #d then @d in the store, without online name resolution
|
||||
| resolveMode == PRMNever -> connectPlanNoName $ ChatError CENotResolvedLocally
|
||||
| otherwise ->
|
||||
tryAllErrors (resolveNameRecord user nm d) >>= \case
|
||||
tryAllErrors (resolveConnectableName user nm d) >>= \case
|
||||
Right nr
|
||||
| isJust (firstNameLink CCTChannel (nrSimplexChannel nr)) ->
|
||||
(addOther nr <$> connectPlanName NTPublicGroup (Right nr)) `catchAllErrors` \e ->
|
||||
@@ -4533,10 +4561,12 @@ processChatCommand cxt nm = \case
|
||||
(\(l, p) -> (l, simplexName_, Nothing, p)) <$> case ctType of
|
||||
CCTContact ->
|
||||
knownLinkPlans >>= \case
|
||||
Just r -> pure r
|
||||
Just r -> knownNamePlan r contactLinkPlan
|
||||
Nothing -> do
|
||||
when (resolveMode == PRMNever) $ throwChatError CENotResolvedLocally
|
||||
l' <- resolveSLink
|
||||
resolveSLink >>= contactLinkPlan Nothing
|
||||
where
|
||||
contactLinkPlan knownChat_ l' = do
|
||||
(FixedLinkData {rootKey}, cData, cReq) <- getShortLinkConnReq nm user l'
|
||||
let presHeader = linkPresHeader l'
|
||||
contactSLinkData_ <- mapM (linkDataBadge presHeader) =<< liftIO (decodeLinkUserData cData)
|
||||
@@ -4545,33 +4575,39 @@ processChatCommand cxt nm = \case
|
||||
planDomain = case nl of CTName ni -> Just (nameDomain ni); _ -> Nothing
|
||||
refreshContact ct' = case (planDomain, linkProfile_) of
|
||||
(Just _, Just p) -> updateContactFromLinkData user ct' presHeader p
|
||||
_ -> pure ct'
|
||||
_ -> pure (ct', ([], []))
|
||||
knownMoved moved = NCMoved . movedChat moved <$> knownChat_
|
||||
forM_ planDomain $ \nameDomain ->
|
||||
unless (linkDomain_ == Just nameDomain) $ throwChatError $ CESimplexDomainNotReady nameDomain SDEUnknownDomain
|
||||
withFastStore' (\db -> getContactWithoutConnViaShortAddress db cxt user l') >>= \case
|
||||
Just ct' | not (contactDeleted ct') -> do
|
||||
ct'' <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct''))
|
||||
(ct'', moved) <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct'') (knownMoved moved))
|
||||
_ -> do
|
||||
let ContactLinkData _ UserContactData {owners} = cData
|
||||
ov = verifyLinkOwner rootKey owners l' sig_
|
||||
plan <- contactRequestPlan user cReq contactSLinkData_ ov
|
||||
plan <- setNameChange (NCMoved <$> knownChat_) <$> contactRequestPlan user cReq contactSLinkData_ ov
|
||||
case plan of
|
||||
CPContactAddress (CAPKnown ct') -> do
|
||||
ct'' <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPKnown ct''))
|
||||
CPContactAddress (CAPContactViaAddress ct') -> do
|
||||
ct'' <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct''))
|
||||
CPContactAddress (CAPKnown ct') _ -> do
|
||||
(ct'', moved) <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPKnown ct'') (knownMoved moved))
|
||||
CPContactAddress (CAPContactViaAddress ct') _ -> do
|
||||
(ct'', moved) <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct'') (knownMoved moved))
|
||||
CPGroupLink (GLPKnown g@GroupInfo {businessChat = Just _} u o os) _ | Just d <- planDomain -> do
|
||||
(g', moved) <- withFastStore' $ \db -> do
|
||||
g' <- setPreparedGroupDomain db user g d
|
||||
(g',) <$> setOtherNameChatsMoved db user (GroupChat g' Nothing)
|
||||
nameMovedToView user moved
|
||||
pure (con l' cReq, CPGroupLink (GLPKnown g' u o os) (knownMoved moved))
|
||||
_ -> pure (con l' cReq, plan)
|
||||
where
|
||||
knownLinkPlans :: CM (Maybe (ACreatedConnLink, ConnectionPlan))
|
||||
knownLinkPlans = withFastStore $ \db ->
|
||||
liftIO (getUserContactLinkViaTarget db user nl') >>= \case
|
||||
Just UserContactLink {connLinkContact} -> pure $ Just (ACCL SCMContact connLinkContact, CPContactAddress CAPOwnLink)
|
||||
Just UserContactLink {connLinkContact} -> pure $ Just (ACCL SCMContact connLinkContact, CPContactAddress CAPOwnLink Nothing)
|
||||
Nothing ->
|
||||
getContactToConnect db cxt user nl' >>= \case
|
||||
Just (ccl, ct') -> pure $ if contactDeleted ct' then Nothing else Just (ACCL SCMContact ccl, CPContactAddress (CAPKnown ct'))
|
||||
Just (ccl, ct') -> pure $ if contactDeleted ct' then Nothing else Just (ACCL SCMContact ccl, CPContactAddress (CAPKnown ct') Nothing)
|
||||
Nothing -> (gPlan =<<) <$> getGroupToConnect db cxt user nl'
|
||||
CCTGroup -> groupShortLinkPlan
|
||||
CCTChannel -> groupShortLinkPlan
|
||||
@@ -4588,21 +4624,26 @@ processChatCommand cxt nm = \case
|
||||
CTLink l' -> pure l'
|
||||
CTName n -> serverShortLink <$> resolveNameLink n
|
||||
con l' cReq = ACCL SCMContact $ CCLink cReq (Just l')
|
||||
gPlan (ccl, g) = if memberRemoved (membership g) then Nothing else Just (ACCL SCMContact ccl, CPGroupLink (GLPKnown g False Nothing (ListDef [])))
|
||||
gPlan (ccl, g) = if memberRemoved (membership g) then Nothing else Just (ACCL SCMContact ccl, CPGroupLink (GLPKnown g False Nothing (ListDef [])) Nothing)
|
||||
groupShortLinkPlan :: CM (ACreatedConnLink, ConnectionPlan)
|
||||
groupShortLinkPlan =
|
||||
knownLinkPlans >>= \case
|
||||
Just (_, CPGroupLink (GLPKnown g _ _ _))
|
||||
| resolveMode == PRMAllGroups -> resolveKnownGroup g
|
||||
Just r -> pure r
|
||||
Just (_, CPGroupLink (GLPKnown g _ _ _) _)
|
||||
| resolveMode == PRMAllGroups, isNothing simplexName_ -> resolveKnownGroup g
|
||||
Just r -> knownNamePlan r groupLinkPlan
|
||||
Nothing -> do
|
||||
when (resolveMode == PRMNever) $ throwChatError CENotResolvedLocally
|
||||
l' <- resolveSLink
|
||||
resolveSLink >>= groupLinkPlan Nothing
|
||||
where
|
||||
groupLinkPlan knownChat_ l' = do
|
||||
(fd, cData@(ContactLinkData _ UserContactData {direct, owners, relays}), cReq) <- getShortLinkConnReq' nm user l'
|
||||
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
|
||||
if
|
||||
| not direct && unsupportedGroupType groupSLinkData_ -> pure (con l' cReq, CPGroupLink (GLPUpdateRequired groupSLinkData_))
|
||||
| not direct && null relays -> pure (con l' cReq, CPGroupLink (GLPNoRelays groupSLinkData_))
|
||||
let planDomain = case nl of CTName ni -> Just (nameDomain ni); _ -> Nothing
|
||||
nc_ = NCMoved <$> knownChat_
|
||||
knownMoved moved = NCMoved . movedChat moved <$> knownChat_
|
||||
plan <- if
|
||||
| not direct && unsupportedGroupType groupSLinkData_ -> pure $ CPGroupLink (GLPUpdateRequired groupSLinkData_) nc_
|
||||
| not direct && null relays -> pure $ CPGroupLink (GLPNoRelays groupSLinkData_) nc_
|
||||
| otherwise -> do
|
||||
let FixedLinkData {linkEntityId, rootKey} = fd
|
||||
linkInfo = GroupShortLinkInfo {direct, groupRelays = relays, publicGroupId = B64UrlByteString <$> linkEntityId}
|
||||
@@ -4614,32 +4655,30 @@ processChatCommand cxt nm = \case
|
||||
_ -> throwChatError CEInvalidConnReq
|
||||
let ov = verifyLinkOwner rootKey owners l' sig_
|
||||
glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners
|
||||
planDomain = case nl of CTName ni -> Just (nameDomain ni); _ -> Nothing
|
||||
plan0 <- groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_ ov glOwners
|
||||
plan0 <- setNameChange nc_ <$> groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_ ov glOwners
|
||||
-- a joined channel is found by link but not by name (its domain is not verified locally,
|
||||
-- e.g. an un-upgraded relay dropped the claim); refresh its profile from the fresh link
|
||||
-- data and mark it verified, so the check below passes and future by-name lookups match
|
||||
plan <- case (planDomain, plan0, groupSLinkData_) of
|
||||
(Just nameDomain, CPGroupLink (GLPKnown g u o os), Just sLinkData) ->
|
||||
(\(g', _) -> CPGroupLink (GLPKnown g' u o os)) <$> updateGroupFromLinkData user g sLinkData (Just nameDomain)
|
||||
case (planDomain, plan0, groupSLinkData_) of
|
||||
(Just nameDomain, CPGroupLink (GLPKnown g u o os) _, Just sLinkData) ->
|
||||
(\(g', _, moved) -> CPGroupLink (GLPKnown g' u o os) (knownMoved moved)) <$> updateGroupFromLinkData user g sLinkData (Just nameDomain)
|
||||
_ -> pure plan0
|
||||
forM_ planDomain $ \nameDomain ->
|
||||
let domain_ = (\GroupProfile {publicGroup} -> claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) =<< case plan of
|
||||
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
|
||||
_ -> (\GroupShortLinkData {groupProfile} -> groupProfile) <$> groupSLinkData_
|
||||
in unless (domain_ == Just nameDomain) $ throwChatError $ CESimplexDomainNotReady nameDomain SDEUnknownDomain
|
||||
pure (con l' cReq, plan)
|
||||
where
|
||||
forM_ planDomain $ \nameDomain ->
|
||||
let domain_ = (\GroupProfile {publicGroup} -> claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) =<< case plan of
|
||||
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
|
||||
_ -> (\GroupShortLinkData {groupProfile} -> groupProfile) <$> groupSLinkData_
|
||||
in unless (domain_ == Just nameDomain) $ throwChatError $ CESimplexDomainNotReady nameDomain SDEUnknownDomain
|
||||
pure (con l' cReq, plan)
|
||||
unsupportedGroupType = \case
|
||||
Just GroupShortLinkData {groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {groupType}}} -> groupType /= GTChannel
|
||||
_ -> False
|
||||
knownLinkPlans :: CM (Maybe (ACreatedConnLink, ConnectionPlan))
|
||||
knownLinkPlans = withFastStore $ \db ->
|
||||
liftIO (getGroupInfoViaUserTarget db cxt user nl') >>= \case
|
||||
Just (ccl, g) -> pure $ Just (ACCL SCMContact ccl, CPGroupLink (GLPOwnLink g))
|
||||
Just (ccl, g) -> pure $ Just (ACCL SCMContact ccl, CPGroupLink (GLPOwnLink g) Nothing)
|
||||
Nothing -> (gPlan =<<) <$> getGroupToConnect db cxt user nl'
|
||||
resolveKnownGroup g = do
|
||||
l' <- resolveSLink
|
||||
@@ -4647,14 +4686,56 @@ processChatCommand cxt nm = \case
|
||||
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
|
||||
let ov = verifyLinkOwner rk owners l' sig_
|
||||
glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners
|
||||
(g', updated) <- case groupSLinkData_ of
|
||||
(g', updated, _) <- case groupSLinkData_ of
|
||||
Just sLinkData -> updateGroupFromLinkData user g sLinkData Nothing
|
||||
_ -> pure (g, False)
|
||||
pure (con l' cReq, CPGroupLink (GLPKnown g' updated ov (ListDef glOwners)))
|
||||
_ -> pure (g, False, ([], []))
|
||||
pure (con l' cReq, CPGroupLink (GLPKnown g' updated ov (ListDef glOwners)) Nothing)
|
||||
movedChat :: ([ContactId], [GroupId]) -> AChatInfo -> AChatInfo
|
||||
movedChat (ctIds, gIds) (AChatInfo t c) = AChatInfo t $ case c of
|
||||
DirectChat ct'@Contact {contactId, profile = p} | contactId `elem` ctIds -> DirectChat ct' {profile = p {contactDomainVerified = Just DVMoved}}
|
||||
GroupChat g@GroupInfo {groupId} s | groupId `elem` gIds -> GroupChat g {groupDomainVerified = Just DVMoved} s
|
||||
_ -> c
|
||||
knownNamePlan :: (ACreatedConnLink, ConnectionPlan) -> (Maybe AChatInfo -> ShortLinkContact -> CM (ACreatedConnLink, ConnectionPlan)) -> CM (ACreatedConnLink, ConnectionPlan)
|
||||
knownNamePlan r@(l, plan) linkPlan
|
||||
| resolveMode == PRMNever || isNothing simplexName_ = pure r
|
||||
| otherwise =
|
||||
tryAllErrors resolveSLink >>= \case
|
||||
Right l'
|
||||
| ACCL SCMContact (CCLink _ (Just sl)) <- l, sameShortLinkContact sl l' -> pure r
|
||||
| otherwise -> (knownChatLink <$> linkPlan knownChat_ l') `catchAllErrors` knownPlanOnError
|
||||
Left (ChatError (CESimplexDomainNotReady _ (SDENameWarning w))) | w /= NWNotRegistered -> pure (l, setNameChange (Just $ NCLapsed w) plan)
|
||||
Left e -> knownPlanOnError e
|
||||
where
|
||||
knownPlanOnError = \case
|
||||
e@ChatErrorAgent {agentError} | temporaryOrHostError agentError -> throwError e
|
||||
e@ChatErrorStore {} -> throwError e
|
||||
e -> r <$ logWarn ("known chat plan used, name lookup error: " <> tshow e)
|
||||
knownChat_ = planChat plan
|
||||
knownChatLink r'@(_, p) = if isKnownChat p then (l, setNameChange Nothing p) else r'
|
||||
isKnownChat p = isJust knownChat_ && refOf (planChat p) == refOf knownChat_
|
||||
refOf = (>>= \(AChatInfo _ c) -> chatInfoToRef c)
|
||||
setNameChange :: Maybe NameChange -> ConnectionPlan -> ConnectionPlan
|
||||
setNameChange nc_ = \case
|
||||
CPContactAddress cap _ -> CPContactAddress cap nc_
|
||||
CPGroupLink glp _ -> CPGroupLink glp nc_
|
||||
p -> p
|
||||
planChat :: ConnectionPlan -> Maybe AChatInfo
|
||||
planChat = \case
|
||||
CPContactAddress cap _ -> AChatInfo SCTDirect . DirectChat <$> case cap of
|
||||
CAPKnown ct' -> Just ct'
|
||||
CAPContactViaAddress ct' -> Just ct'
|
||||
CAPConnectingProhibit ct' -> Just ct'
|
||||
_ -> Nothing
|
||||
CPGroupLink glp _ -> (\g -> AChatInfo SCTGroup $ GroupChat g Nothing) <$> case glp of
|
||||
GLPKnown g _ _ _ -> Just g
|
||||
GLPOwnLink g -> Just g
|
||||
GLPConnectingProhibit g_ -> g_
|
||||
_ -> Nothing
|
||||
_ -> Nothing
|
||||
-- resolve a name to its first contact/channel short link
|
||||
resolveNameLink :: SimplexNameInfo -> CM (ConnShortLink 'CMContact)
|
||||
resolveNameLink SimplexNameInfo {nameType, nameDomain} = do
|
||||
NameRecord {nrSimplexContact, nrSimplexChannel} <- maybe (resolveNameRecord user nm nameDomain) (ExceptT . pure) nameRec
|
||||
NameRecord {nrSimplexContact, nrSimplexChannel} <- maybe (resolveConnectableName user nm nameDomain) (ExceptT . pure) nameRec
|
||||
let (candidates, ctType') = case nameType of
|
||||
NTContact -> (nrSimplexContact, CCTContact)
|
||||
NTPublicGroup -> (nrSimplexChannel, CCTChannel)
|
||||
@@ -4664,10 +4745,10 @@ processChatCommand cxt nm = \case
|
||||
| connectionPlanProceed plan = do
|
||||
case plan of CPError e -> eToView e; _ -> pure ()
|
||||
case plan of
|
||||
CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
|
||||
CPContactAddress (CAPContactViaAddress Contact {contactId}) _ ->
|
||||
processChatCommand cxt nm $ APIConnectContactViaAddress userId incognito contactId
|
||||
CPContactAddress (CAPOk (Just sld) _) | isJust vName -> connectContactViaName sld
|
||||
CPGroupLink (GLPOk (Just GroupShortLinkInfo {direct = False}) (Just gld) _)
|
||||
CPContactAddress (CAPOk (Just sld) _) _ | isJust vName -> connectContactViaName 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
|
||||
@@ -4726,7 +4807,7 @@ processChatCommand cxt nm = \case
|
||||
contactRequestPlan user cReq cld ov = do
|
||||
let cReqSchemas = contactCReqSchemas cReq
|
||||
cReqHashes = bimap contactCReqHash contactCReqHash cReqSchemas
|
||||
plan p = pure $ CPContactAddress p
|
||||
plan p = pure $ CPContactAddress p Nothing
|
||||
withFastStore' (\db -> getUserContactLinkByConnReq db user cReqSchemas) >>= \case
|
||||
Just _ -> plan $ CAPOwnLink
|
||||
Nothing ->
|
||||
@@ -4749,7 +4830,7 @@ processChatCommand cxt nm = \case
|
||||
groupJoinRequestPlan user cReq linkInfo gld ov glOwners = do
|
||||
let cReqSchemas = contactCReqSchemas cReq
|
||||
cReqHashes = bimap contactCReqHash contactCReqHash cReqSchemas
|
||||
plan p = pure $ CPGroupLink p
|
||||
plan p = pure $ CPGroupLink p Nothing
|
||||
withFastStore' (\db -> getGroupInfoByUserContactLinkConnReq db cxt user cReqSchemas) >>= \case
|
||||
Just g -> plan $ GLPOwnLink g
|
||||
Nothing -> do
|
||||
@@ -4772,7 +4853,7 @@ processChatCommand cxt nm = \case
|
||||
| memberActive membership = plan $ GLPKnown gInfo False ov (ListDef glOwners)
|
||||
| otherwise = plan $ GLPOk linkInfo gld ov
|
||||
where
|
||||
plan p = pure $ CPGroupLink p
|
||||
plan p = pure $ CPGroupLink p Nothing
|
||||
contactCReqSchemas :: ConnReqContact -> (ConnReqContact, ConnReqContact)
|
||||
contactCReqSchemas (CRContactUri crData e2e) =
|
||||
( CRContactUri crData {crScheme = SSSimplex} e2e,
|
||||
@@ -5154,7 +5235,11 @@ processChatCommand cxt nm = \case
|
||||
useRelays = not direct
|
||||
subRole <- if useRelays then asks $ channelSubscriberRole . config else pure GRMember
|
||||
gVar <- asks random
|
||||
withStore $ \db -> createPreparedGroup db gVar cxt user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ verifiedDomain
|
||||
(gInfo, hostMember_, moved) <- withStore $ \db -> do
|
||||
(g, hm_) <- createPreparedGroup db gVar cxt user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ verifiedDomain
|
||||
(g, hm_,) <$> if isJust verifiedDomain then liftIO (setOtherNameChatsMoved db user (GroupChat g Nothing)) else pure ([], [])
|
||||
nameMovedToView user moved
|
||||
pure (gInfo, hostMember_)
|
||||
|
||||
getSharedMsgId :: CM SharedMsgId
|
||||
getSharedMsgId = do
|
||||
@@ -5179,6 +5264,27 @@ resolveNameRecord user nm domain = do
|
||||
NRRegistered {nameRecord} -> pure nameRecord
|
||||
_ -> throwError $ chatErrorAgent $ SMP "" (NAME SMP.NOT_FOUND)
|
||||
|
||||
resolveConnectableName :: User -> NetworkRequestMode -> SimplexDomain -> CM NameRecord
|
||||
resolveConnectableName user nm domain = do
|
||||
NameResponse {registration} <- withAgent $ \a -> resolveSimplexName a nm (aUserId user) domain
|
||||
now <- liftIO getSystemSeconds
|
||||
either (throwChatError . CESimplexDomainNotReady domain . SDENameWarning) pure $ nameRecordOrWarning now domain registration
|
||||
|
||||
nameRecordOrWarning :: SystemSeconds -> SimplexDomain -> NameRegistration -> Either NameWarning NameRecord
|
||||
nameRecordOrWarning now SimplexDomain {domain} = \case
|
||||
NRRegistered {expires = Just expires, graceUntil}
|
||||
| expires < now -> Left $ NWExpired (roundedToUTCTime expires) (roundedToUTCTime <$> mfilter (now <=) graceUntil)
|
||||
NRRegistered {nameRecord} -> Right nameRecord
|
||||
NRAvailable {pricing = NamePricing {registrationPrices, basePrice, minLabelLength}}
|
||||
| labelLength >= minLabelLength ->
|
||||
let USDCents perYear = M.findWithDefault basePrice labelLength registrationPrices
|
||||
years = 2
|
||||
in Left $ NWAvailable NamePrice {amount = USDCents (perYear * fromIntegral years), years}
|
||||
NRReserved {reservedReason = NRRCommunity} -> Left NWReservedForCommunity
|
||||
_ -> Left NWNotRegistered
|
||||
where
|
||||
labelLength = T.length domain
|
||||
|
||||
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
|
||||
(Nothing, _) -> pure (Nothing, Just "no name proof to verify")
|
||||
|
||||
@@ -1631,40 +1631,68 @@ updatePublicGroupData user gInfo gks
|
||||
| otherwise = pure gInfo
|
||||
|
||||
-- must not resolve names here: a background link-data refresh would leak channel membership to the resolver
|
||||
updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> Maybe SimplexDomain -> CM (GroupInfo, Bool)
|
||||
updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> Maybe SimplexDomain -> CM (GroupInfo, Bool, ([ContactId], [GroupId]))
|
||||
updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} resolvedDomain_
|
||||
| profileChanged || countChanged || verifyResolved = do
|
||||
cxt <- chatStoreCxt
|
||||
withStore $ \db -> do
|
||||
(gInfo', moved) <- 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 else pure g'
|
||||
pure (g'', profileChanged)
|
||||
| otherwise = pure (gInfo, False)
|
||||
if verifyResolved
|
||||
then liftIO $ do
|
||||
g'' <- setGroupDomainVerified db user g' True
|
||||
(g'',) <$> setOtherNameChatsMoved db user (GroupChat g'' Nothing)
|
||||
else pure (g', ([], []))
|
||||
nameMovedToView user moved
|
||||
pure (gInfo', profileChanged, moved)
|
||||
| otherwise = pure (gInfo, False, ([], []))
|
||||
where
|
||||
profileChanged = p /= groupProfile
|
||||
countChanged = case publicGroupData of
|
||||
Just PublicGroupData {publicMemberCount} -> Just publicMemberCount /= localCount
|
||||
_ -> False
|
||||
groupClaim GroupProfile {publicGroup} = claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)
|
||||
newClaim = groupClaim groupProfile
|
||||
verifyResolved = isJust resolvedDomain_ && resolvedDomain_ == newClaim
|
||||
|
||||
updateContactFromLinkData :: User -> Contact -> ProofPresHeader -> Profile -> CM Contact
|
||||
groupClaim :: GroupProfile -> Maybe SimplexDomain
|
||||
groupClaim GroupProfile {publicGroup} = claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)
|
||||
|
||||
updateContactFromLinkData :: User -> Contact -> ProofPresHeader -> Profile -> CM (Contact, ([ContactId], [GroupId]))
|
||||
updateContactFromLinkData user ct@Contact {profile = profile@LocalProfile {contactDomain = prevClaim, contactDomainVerified}} presHeader linkProfile@Profile {contactDomain = newClaim}
|
||||
| profileChanged || verifyChanged = do
|
||||
cxt <- chatStoreCxt
|
||||
withFastStore $ \db -> do
|
||||
r@(_, moved) <- withFastStore $ \db -> do
|
||||
ct' <- updateContactProfile db cxt user ct (Just presHeader) linkProfile
|
||||
if verifyChanged then liftIO $ setContactDomainVerified db user ct' True else pure ct'
|
||||
| otherwise = pure ct
|
||||
if verifyChanged
|
||||
then liftIO $ do
|
||||
ct'' <- setContactDomainVerified db user ct' True
|
||||
(ct'',) <$> setOtherNameChatsMoved db user (DirectChat ct'')
|
||||
else pure (ct', ([], []))
|
||||
nameMovedToView user moved
|
||||
pure r
|
||||
| otherwise = pure (ct, ([], []))
|
||||
where
|
||||
profileChanged = fromLocalProfile profile /= linkProfile
|
||||
claimChanged = (claimDomain <$> prevClaim) /= (claimDomain <$> newClaim)
|
||||
verifyChanged = contactDomainVerified /= Just True || claimChanged
|
||||
verifyChanged = contactDomainVerified /= Just DVVerified || claimChanged
|
||||
|
||||
setOtherNameChatsMoved :: DB.Connection -> User -> ChatInfo c -> IO ([ContactId], [GroupId])
|
||||
setOtherNameChatsMoved db user = \case
|
||||
DirectChat Contact {contactId, profile = LocalProfile {contactDomain = Just d}} -> setMoved (SimplexNameInfo NTContact $ claimDomain d) (Just contactId) Nothing
|
||||
GroupChat GroupInfo {groupId, businessChat = Just BusinessChatInfo {businessDomain = Just d}} _ -> setMoved (SimplexNameInfo NTContact $ claimDomain d) Nothing (Just groupId)
|
||||
GroupChat GroupInfo {groupId, businessChat = Nothing, groupProfile} _ | Just d <- groupClaim groupProfile -> setMoved (SimplexNameInfo NTPublicGroup d) Nothing (Just groupId)
|
||||
_ -> pure ([], [])
|
||||
where
|
||||
setMoved ni@SimplexNameInfo {nameType, nameDomain} ctId_ gId_ =
|
||||
(,)
|
||||
<$> (if nameType == NTContact then setNameContactsMoved db user nameDomain ctId_ else pure [])
|
||||
<*> setNameGroupsMoved db user ni gId_
|
||||
|
||||
nameMovedToView :: User -> ([ContactId], [GroupId]) -> CM ()
|
||||
nameMovedToView user (ctIds, gIds) = unless (null ctIds && null gIds) $ toView $ CEvtNameMoved user ctIds gIds
|
||||
|
||||
-- TODO [relays] owner: set owners on updating link data (multi-owner)
|
||||
groupLinkData :: GroupInfoKeys -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData)
|
||||
|
||||
@@ -52,6 +52,7 @@ module Simplex.Chat.Store.Direct
|
||||
getContactIdByName,
|
||||
updateContactProfile,
|
||||
setContactDomainVerified,
|
||||
setNameContactsMoved,
|
||||
updateContactUserPreferences,
|
||||
updateContactAlias,
|
||||
updateContactConnectionAlias,
|
||||
@@ -115,7 +116,7 @@ import Simplex.Chat.Names (SimplexDomainClaim (..))
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionModeI (..), ConnectionRequestUri, CreatedConnLink (..), SConnectionMode (..), SimplexNameInfo (..), UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionModeI (..), ConnectionRequestUri, CreatedConnLink (..), SConnectionMode (..), SimplexDomain, SimplexNameInfo (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
@@ -599,7 +600,22 @@ setContactDomainVerified db User {userId} ct@Contact {contactId, profile = p} ve
|
||||
WHERE contact_profile_id IN (SELECT contact_profile_id FROM contacts WHERE user_id = ? AND contact_id = ?)
|
||||
|]
|
||||
(BI verified, userId, contactId)
|
||||
pure (ct {profile = p {contactDomainVerified = Just verified}} :: Contact)
|
||||
pure (ct {profile = p {contactDomainVerified = Just $ if verified then DVVerified else DVFailed}} :: Contact)
|
||||
|
||||
setNameContactsMoved :: DB.Connection -> User -> SimplexDomain -> Maybe ContactId -> IO [ContactId]
|
||||
setNameContactsMoved db User {userId} domain exceptContactId_ = do
|
||||
cts <-
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT ct.contact_id, ct.contact_profile_id FROM contacts ct
|
||||
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|
||||
WHERE ct.user_id = ? AND ct.contact_id IS DISTINCT FROM ? AND cp.contact_domain = ? AND cp.contact_domain_verified = 1
|
||||
|]
|
||||
(userId, exceptContactId_, domain)
|
||||
forM_ cts $ \(_ :: ContactId, profileId :: ProfileId) ->
|
||||
DB.execute db "UPDATE contact_profiles SET contact_domain_verified = 2 WHERE contact_profile_id = ?" (Only profileId)
|
||||
pure $ map fst cts
|
||||
|
||||
updateContactUserPreferences :: DB.Connection -> User -> Contact -> Preferences -> IO Contact
|
||||
updateContactUserPreferences db user@User {userId} c@Contact {contactId} userPreferences = do
|
||||
|
||||
@@ -47,6 +47,8 @@ module Simplex.Chat.Store.Groups
|
||||
getGroupInfoByGroupLinkHash,
|
||||
updateGroupProfile,
|
||||
setGroupDomainVerified,
|
||||
setNameGroupsMoved,
|
||||
setPreparedGroupDomain,
|
||||
updateGroupPreferences,
|
||||
updateGroupProfileFromMember,
|
||||
getGroupIdByName,
|
||||
@@ -234,7 +236,7 @@ import qualified Data.Text as T
|
||||
import Data.Time.Clock (NominalDiffTime, UTCTime (..), addUTCTime, getCurrentTime)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Simplex.Chat.Badges (BadgeProof, BadgeRow, ProofPresHeader, acceptedBadge, badgeProofToRow, badgeToRow, sameBadgeProof, verifyBadge)
|
||||
import Simplex.Chat.Names (SimplexDomainClaim (..))
|
||||
import Simplex.Chat.Names (SimplexDomainClaim (..), mkDomainClaim)
|
||||
import Simplex.Chat.Messages
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Protocol hiding (Binary)
|
||||
@@ -2787,12 +2789,30 @@ setGroupDomainVerified db User {userId} g@GroupInfo {groupId} verified = do
|
||||
db
|
||||
"UPDATE groups SET group_domain_verified = ? WHERE user_id = ? AND group_id = ?"
|
||||
(BI verified, userId, groupId)
|
||||
pure g {groupDomainVerified = Just verified}
|
||||
pure g {groupDomainVerified = Just $ if verified then DVVerified else DVFailed}
|
||||
|
||||
setNameGroupsMoved :: DB.Connection -> User -> SimplexNameInfo -> Maybe GroupId -> IO [GroupId]
|
||||
setNameGroupsMoved db User {userId} SimplexNameInfo {nameType, nameDomain} exceptGroupId_ =
|
||||
map fromOnly <$> DB.query
|
||||
db
|
||||
( [sql|
|
||||
UPDATE groups SET group_domain_verified = 2
|
||||
WHERE user_id = ? AND group_domain_verified = 1 AND group_id IS DISTINCT FROM ?
|
||||
AND group_profile_id IN (SELECT group_profile_id FROM group_profiles WHERE user_id = ? AND group_domain = ?)
|
||||
|]
|
||||
<> businessCond
|
||||
<> " RETURNING group_id"
|
||||
)
|
||||
(userId, exceptGroupId_, userId, nameDomain)
|
||||
where
|
||||
businessCond = case nameType of
|
||||
NTContact -> " AND business_chat IS NOT NULL"
|
||||
NTPublicGroup -> " AND business_chat IS NULL"
|
||||
|
||||
-- A business group has no publicGroup claim, so the domain it was connected by (from its address) is written
|
||||
-- directly to group_domain and marked verified, so it is found by the local name search (getGroupToConnect).
|
||||
setPreparedGroupDomain :: DB.Connection -> User -> GroupInfo -> SimplexDomain -> IO GroupInfo
|
||||
setPreparedGroupDomain db user@User {userId} g@GroupInfo {groupId} domain = do
|
||||
setPreparedGroupDomain db user@User {userId} g@GroupInfo {groupId, businessChat} domain = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -2800,7 +2820,7 @@ setPreparedGroupDomain db user@User {userId} g@GroupInfo {groupId} domain = do
|
||||
WHERE group_profile_id IN (SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?)
|
||||
|]
|
||||
(domain, userId, groupId)
|
||||
setGroupDomainVerified db user g True
|
||||
setGroupDomainVerified db user g {businessChat = (\bc -> bc {businessDomain = Just $ mkDomainClaim domain}) <$> businessChat} True
|
||||
|
||||
updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo
|
||||
updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do
|
||||
@@ -2887,7 +2907,7 @@ getGroupInfoViaUserTarget db cxt user@User {userId} target = fmap eitherToMaybe
|
||||
FROM user_contact_links ucl
|
||||
JOIN groups g ON g.group_id = ucl.group_id
|
||||
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
|
||||
WHERE ucl.user_id = ? AND gp.group_domain = ?
|
||||
WHERE ucl.user_id = ? AND gp.group_domain = ? AND g.group_domain_verified IS DISTINCT FROM 2
|
||||
|]
|
||||
(userId, nameDomain ni)
|
||||
toConnReqGroupId = \case
|
||||
|
||||
@@ -484,7 +484,7 @@ Query:
|
||||
FROM user_contact_links ucl
|
||||
JOIN groups g ON g.group_id = ucl.group_id
|
||||
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
|
||||
WHERE ucl.user_id = ? AND gp.group_domain = ?
|
||||
WHERE ucl.user_id = ? AND gp.group_domain = ? AND g.group_domain_verified IS DISTINCT FROM 2
|
||||
|
||||
Plan:
|
||||
SEARCH ucl USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
|
||||
@@ -3849,6 +3849,15 @@ Plan:
|
||||
SEARCH ct USING INDEX idx_contacts_chat_ts (user_id=?)
|
||||
SEARCH cp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT ct.contact_id, ct.contact_profile_id FROM contacts ct
|
||||
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|
||||
WHERE ct.user_id = ? AND ct.contact_id IS DISTINCT FROM ? AND cp.contact_domain = ? AND cp.contact_domain_verified = 1
|
||||
|
||||
Plan:
|
||||
SEARCH ct USING COVERING INDEX sqlite_autoindex_contacts_2 (user_id=?)
|
||||
SEARCH cp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT d.file_descr_id, d.file_descr_text, d.file_descr_part_no, d.file_descr_complete
|
||||
FROM xftp_file_descriptions d
|
||||
@@ -4434,6 +4443,26 @@ Query:
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups SET group_domain_verified = 2
|
||||
WHERE user_id = ? AND group_domain_verified = 1 AND group_id IS DISTINCT FROM ?
|
||||
AND group_profile_id IN (SELECT group_profile_id FROM group_profiles WHERE user_id = ? AND group_domain = ?)
|
||||
AND business_chat IS NOT NULL RETURNING group_id
|
||||
Plan:
|
||||
SEARCH groups USING INDEX sqlite_autoindex_groups_2 (user_id=? AND group_profile_id=?)
|
||||
LIST SUBQUERY 1
|
||||
SEARCH group_profiles USING INDEX idx_group_profiles_user_id (user_id=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups SET group_domain_verified = 2
|
||||
WHERE user_id = ? AND group_domain_verified = 1 AND group_id IS DISTINCT FROM ?
|
||||
AND group_profile_id IN (SELECT group_profile_id FROM group_profiles WHERE user_id = ? AND group_domain = ?)
|
||||
AND business_chat IS NULL RETURNING group_id
|
||||
Plan:
|
||||
SEARCH groups USING INDEX sqlite_autoindex_groups_2 (user_id=? AND group_profile_id=?)
|
||||
LIST SUBQUERY 1
|
||||
SEARCH group_profiles USING INDEX idx_group_profiles_user_id (user_id=?)
|
||||
|
||||
Query:
|
||||
WITH user_groups AS MATERIALIZED (
|
||||
SELECT g.group_id
|
||||
@@ -6637,6 +6666,13 @@ Query:
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, short_link_large_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND group_id IS NULL AND short_link_contact IS NOT NULL
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX idx_user_contact_links_group_id (group_id=?)
|
||||
|
||||
Query:
|
||||
SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, short_link_large_data_set, business_address, auto_accept, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
@@ -8042,6 +8078,10 @@ Query: UPDATE contact_profiles SET contact_domain = ?, updated_at = ? WHERE user
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contact_profiles SET contact_domain_verified = 2 WHERE contact_profile_id = ?
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contact_profiles SET contact_domain_verified = NULL WHERE user_id = ? AND contact_profile_id = ?
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -494,7 +494,7 @@ type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe
|
||||
|
||||
type ContactRow = Only ContactId :. ContactRow'
|
||||
|
||||
type ContactDomainRow = (Maybe SimplexDomain, Maybe SimplexDomainProof, Maybe BoolInt)
|
||||
type ContactDomainRow = (Maybe SimplexDomain, Maybe SimplexDomainProof, Maybe DomainVerification)
|
||||
|
||||
toContact :: UTCTime -> StoreCxt -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact
|
||||
toContact now cxt user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, encodedPrefs, receivedPrefs, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, rejectionSupported_, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow :. domainRow) :. connRow) =
|
||||
@@ -512,8 +512,8 @@ toContact now cxt user chatTags ((Only contactId :. (profileId, localDisplayName
|
||||
rowToContactDomain :: ContactDomainRow -> Maybe SimplexDomainClaim
|
||||
rowToContactDomain (domain_, domainProof_, _) = (`SimplexDomainClaim` domainProof_) . StrJSON <$> domain_
|
||||
|
||||
rowToDomainVerified :: ContactDomainRow -> Maybe Bool
|
||||
rowToDomainVerified (_, _, domainVerification_) = unBI <$> domainVerification_
|
||||
rowToDomainVerified :: ContactDomainRow -> Maybe DomainVerification
|
||||
rowToDomainVerified (_, _, domainVerification_) = domainVerification_
|
||||
|
||||
contactDomainToRow :: Maybe SimplexDomainClaim -> (Maybe SimplexDomain, Maybe SimplexDomainProof)
|
||||
contactDomainToRow d = (claimDomain <$> d, proof =<< d)
|
||||
@@ -696,7 +696,7 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member
|
||||
|
||||
type GroupKeysRow = (Maybe C.PrivateKeyEd25519, Maybe C.PublicKeyEd25519, Maybe C.PrivateKeyEd25519)
|
||||
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. PublicGroupAccessRow :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Text, Maybe Text, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe VersionRoster, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact, Maybe BoolInt) :. GroupKeysRow :. GroupMemberRow
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. PublicGroupAccessRow :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Text, Maybe Text, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe VersionRoster, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact, Maybe DomainVerification) :. GroupKeysRow :. GroupMemberRow
|
||||
|
||||
type PublicGroupAccessRow = (Maybe Text, Maybe SimplexDomain, Maybe BoolInt, Maybe BoolInt, Maybe SimplexDomainProof)
|
||||
|
||||
@@ -715,7 +715,7 @@ toGroupInfo now cxt userContactId chatTags ((groupId, localDisplayName, displayN
|
||||
businessChat = toBusinessChatInfo (toPublicGroupAccess accessRow >>= groupDomainClaim) businessRow
|
||||
preparedGroup = toPreparedGroup preparedGroupRow
|
||||
groupSummary = GroupSummary {currentMembers, publicMemberCount}
|
||||
gInfo = GroupInfo {groupId, useRelays = BoolDef useRelays, relayOwnStatus, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, groupSummary, rosterVersion, customData, membersRequireAttention, viaGroupLinkUri, groupDomainVerified = unBI <$> groupDomainVerified}
|
||||
gInfo = GroupInfo {groupId, useRelays = BoolDef useRelays, relayOwnStatus, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, groupSummary, rosterVersion, customData, membersRequireAttention, viaGroupLinkUri, groupDomainVerified}
|
||||
in (gInfo, groupKeysRow)
|
||||
|
||||
toGroupInfo_ :: UTCTime -> StoreCxt -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
|
||||
|
||||
@@ -537,7 +537,7 @@ data GroupInfo = GroupInfo
|
||||
rosterVersion :: Maybe VersionRoster,
|
||||
membersRequireAttention :: Int,
|
||||
viaGroupLinkUri :: Maybe ConnReqContact,
|
||||
groupDomainVerified :: Maybe Bool
|
||||
groupDomainVerified :: Maybe DomainVerification
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -831,14 +831,17 @@ data LocalProfile = LocalProfile
|
||||
localBadge :: Maybe LocalBadge,
|
||||
localAlias :: LocalAlias,
|
||||
contactDomain :: Maybe SimplexDomainClaim,
|
||||
contactDomainVerified :: Maybe Bool
|
||||
contactDomainVerified :: Maybe DomainVerification
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data DomainVerification = DVFailed | DVVerified | DVMoved
|
||||
deriving (Eq, Show)
|
||||
|
||||
localProfileId :: LocalProfile -> ProfileId
|
||||
localProfileId LocalProfile {profileId} = profileId
|
||||
|
||||
toLocalProfile :: ProfileId -> Profile -> LocalAlias -> UTCTime -> Maybe Bool -> Maybe Bool -> LocalProfile
|
||||
toLocalProfile :: ProfileId -> Profile -> LocalAlias -> UTCTime -> Maybe Bool -> Maybe DomainVerification -> LocalProfile
|
||||
toLocalProfile profileId Profile {displayName, fullName, shortDescr, description, image, contactLink, preferences, peerType, badge, contactDomain} localAlias now badgeVerified contactDomainVerified =
|
||||
LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, preferences, peerType, localBadge, localAlias, contactDomain, contactDomainVerified}
|
||||
where
|
||||
@@ -2319,6 +2322,8 @@ $(JQ.deriveJSON defaultJSON ''UserContact)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''Profile)
|
||||
|
||||
$(JQ.deriveJSON (enumJSON $ dropPrefix "DV") ''DomainVerification)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''LocalProfile)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''UserContactRequest)
|
||||
@@ -2388,6 +2393,13 @@ instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP
|
||||
|
||||
instance ToField MsgFilter where toField = toField . msgFilterInt
|
||||
|
||||
instance FromField DomainVerification where
|
||||
fromField = fromIntField_ $ \case
|
||||
0 -> Just DVFailed
|
||||
1 -> Just DVVerified
|
||||
2 -> Just DVMoved
|
||||
_ -> Nothing
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''CReqClientData)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''MemberIdRole)
|
||||
|
||||
+47
-10
@@ -73,7 +73,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, BlockingInfo (..), BlockingReason (..), NetworkError (..), ProtocolServer (..), ProtocolTypeI, SProtocolType (..), UserProtocol)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, BlockingInfo (..), BlockingReason (..), NetworkError (..), ProtocolServer (..), ProtocolTypeI, SProtocolType (..), USDCents (..), UserProtocol)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
|
||||
@@ -218,7 +218,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 _ otherSimplexName connectionPlan -> ttyUser u $ viewConnectionPlan cfg connLink connectionPlan <> otherSimplexNameNote otherSimplexName
|
||||
CRConnectionPlan u connLink planSimplexName otherSimplexName connectionPlan -> ttyUser u $ viewConnectionPlan cfg connLink planSimplexName connectionPlan <> otherSimplexNameNote otherSimplexName
|
||||
CRNewPreparedChat u (AChat _ (Chat cInfo _ _)) -> ttyUser u $ case cInfo of
|
||||
DirectChat ct -> [ttyContact' ct <> ": contact is prepared"]
|
||||
GroupChat g _ -> [ttyGroup' g <> ": group is prepared"]
|
||||
@@ -474,6 +474,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft
|
||||
CEvtRcvFileProgressXFTP {} -> []
|
||||
CEvtContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c'
|
||||
CEvtNameMoved {} -> []
|
||||
CEvtGroupMemberUpdated {} -> []
|
||||
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} _chat -> ttyUser u $ viewReceivedContactRequest c (fromLocalProfile profile)
|
||||
CEvtServiceRequest u reqId sigKey_ req ->
|
||||
@@ -1171,13 +1172,14 @@ viewDomainVerified nameType domain_ result =
|
||||
Just reason -> [plain nameStr <> " not verified: " <> plain reason]
|
||||
|
||||
-- §4.7: show a peer's claimed name only with its verification context — "verified" / "verification
|
||||
-- failed" when a status is recorded, "unverified" when there is a proof but no status yet, and nothing
|
||||
-- failed" / "moved" when a status is recorded, "unverified" when there is a proof but no status yet, and nothing
|
||||
-- at all when there is neither (an unproven, unverifiable claim is not shown).
|
||||
simplexDomainLine :: SimplexNameType -> Maybe SimplexDomainClaim -> Maybe Bool -> [StyledString]
|
||||
simplexDomainLine :: SimplexNameType -> Maybe SimplexDomainClaim -> Maybe DomainVerification -> [StyledString]
|
||||
simplexDomainLine _ Nothing _ = []
|
||||
simplexDomainLine nameType (Just SimplexDomainClaim {domain, proof}) status = case status of
|
||||
Just True -> [line "verified"]
|
||||
Just False -> [line "verification failed"]
|
||||
Just DVVerified -> [line "verified"]
|
||||
Just DVFailed -> [line "verification failed"]
|
||||
Just DVMoved -> [line "moved"]
|
||||
Nothing
|
||||
| isJust proof -> [line "unverified"]
|
||||
| otherwise -> []
|
||||
@@ -2235,8 +2237,8 @@ otherSimplexNameNote = \case
|
||||
Just ni@(SimplexNameInfo NTContact _) -> [plain $ "You can also connect to " <> shortNameInfoStr ni <> " in direct chat"]
|
||||
Nothing -> []
|
||||
|
||||
viewConnectionPlan :: ChatConfig -> ACreatedConnLink -> ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
viewConnectionPlan :: ChatConfig -> ACreatedConnLink -> Maybe SimplexNameInfo -> ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} _connLink planSimplexName = \case
|
||||
CPInvitationLink ilp -> case ilp of
|
||||
ILPOk contactSLinkData ov -> [invOrBiz contactSLinkData "ok to connect"] <> viewSigVerification ov <> [viewJSON contactSLinkData | testView]
|
||||
ILPOwnLink -> [invLink "own link"]
|
||||
@@ -2255,7 +2257,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
Just ContactShortLinkData {business}
|
||||
| business -> ("business address: " <>)
|
||||
_ -> ("invitation link: " <>)
|
||||
CPContactAddress cap -> case cap of
|
||||
p@(CPContactAddress cap nc_) -> (<> viewNameChange (connectionPlanOwnLink p) nc_) $ case cap of
|
||||
CAPOk contactSLinkData ov -> [addrOrBiz contactSLinkData "ok to connect"] <> viewSigVerification ov <> [viewJSON contactSLinkData | testView]
|
||||
CAPOwnLink -> [ctAddr "own address"]
|
||||
CAPConnectingConfirmReconnect -> [ctAddr "connecting, allowed to reconnect"]
|
||||
@@ -2273,7 +2275,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
Just ContactShortLinkData {business}
|
||||
| business -> ("business address: " <>)
|
||||
_ -> ("contact address: " <>)
|
||||
CPGroupLink glp -> case glp of
|
||||
p@(CPGroupLink glp nc_) -> (<> viewNameChange (connectionPlanOwnLink p) nc_) $ case glp of
|
||||
GLPOk groupSLinkInfo_ groupSLinkData ov ->
|
||||
let direct = maybe True (\(GroupShortLinkInfo {direct = d}) -> d) groupSLinkInfo_
|
||||
in [grpLink $ if direct then "ok to connect directly" else "ok to connect via relays"]
|
||||
@@ -2312,6 +2314,17 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
Nothing -> "group"
|
||||
CPError e -> viewChatError False logLevel testView e
|
||||
where
|
||||
viewNameChange :: Bool -> Maybe NameChange -> [StyledString]
|
||||
viewNameChange own = \case
|
||||
Just (NCLapsed w) -> maybe [] (\SimplexNameInfo {nameDomain} -> [viewNameWarning own nameDomain w]) planSimplexName
|
||||
Just (NCMoved (AChatInfo _ c)) -> case c of
|
||||
DirectChat ct -> ["known contact @" <> ttyContact' ct]
|
||||
GroupChat g@GroupInfo {businessChat} _
|
||||
| isJust businessChat -> ["known business " <> ttyGroup' g]
|
||||
| useRelays' g -> ["known channel " <> ttyGroup' g]
|
||||
| otherwise -> ["known group " <> ttyGroup' g]
|
||||
_ -> []
|
||||
Nothing -> []
|
||||
nextConnectPrepared Contact {preparedContact, activeConn} = case preparedContact of
|
||||
Just _ -> maybe True (\c -> connStatus c == ConnPrepared) activeConn
|
||||
_ -> False
|
||||
@@ -2327,6 +2340,29 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
Just (OVFailed r) -> ["owner signature: FAILED (" <> plain r <> ")"]
|
||||
Nothing -> []
|
||||
|
||||
viewNameWarning :: Bool -> SimplexDomain -> NameWarning -> StyledString
|
||||
viewNameWarning own d = \case
|
||||
NWExpired e g -> yours <> simplexNameStr d <> " expired on " <> plain (day e) <> maybe "" ((renewal <>) . plain . day) g
|
||||
NWAvailable p -> yours <> simplexNameStr d <> " is no longer registered, available: " <> namePriceStr p
|
||||
NWReservedForCommunity -> simplexNameStr d <> " is reserved for community"
|
||||
NWNotRegistered -> simplexNameStr d <> " is not registered"
|
||||
where
|
||||
yours = if own then "your " else ""
|
||||
renewal = if own then ", renew it before " else ", its owner can renew it until "
|
||||
|
||||
viewNameNotConnectable :: SimplexDomain -> NameWarning -> StyledString
|
||||
viewNameNotConnectable d = \case
|
||||
NWAvailable p -> simplexNameStr d <> " is available: " <> namePriceStr p
|
||||
w -> viewNameWarning False d w
|
||||
|
||||
simplexNameStr :: SimplexDomain -> StyledString
|
||||
simplexNameStr d = "SimpleX name " <> plain (fullDomainName d)
|
||||
|
||||
namePriceStr :: NamePrice -> StyledString
|
||||
namePriceStr NamePrice {amount = USDCents c, years} =
|
||||
let (dollars, cents) = c `divMod` 100
|
||||
in plain $ "$" <> tshow dollars <> (if cents == 0 then "" else "." <> T.justifyRight 2 '0' (tshow cents)) <> " for " <> tshow years <> " years"
|
||||
|
||||
viewContactUpdated :: Contact -> Contact -> [StyledString]
|
||||
viewContactUpdated
|
||||
Contact {localDisplayName = n, profile = LocalProfile {fullName, shortDescr, description, contactLink}}
|
||||
@@ -2795,6 +2831,7 @@ viewChatError isCmd logLevel testView = \case
|
||||
CEChatNotStopped -> ["error: chat not stopped"]
|
||||
CEChatStoreChanged -> ["error: chat store changed, please restart chat"]
|
||||
CEInvalidConnReq -> viewInvalidConnReq
|
||||
CESimplexDomainNotReady domain (SDENameWarning w) -> [viewNameNotConnectable domain w]
|
||||
CESimplexDomainNotReady domain domainErr ->
|
||||
let reason = case domainErr of
|
||||
SDENoValidLink -> "has no valid connection link"
|
||||
|
||||
Reference in new issue
Block a user