mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-19 00:47:03 +00:00
android, desktop, ios: connect via SimpleX name (#7068)
* android, desktop, ios: connect via SimpleX name
* android, desktop, ios: open known contact on name lookup; surface prepared contact
Name search opens the contact (not list-filter); resolved/prepared contacts and groups are added to the chat list so they're visible and openable. Kotlin compile-verified; iOS edits pattern-matched, pending Xcode build.
* feat(names): UI names role + agent NAME error
Parity with the core names rework (#7045):
- Add `names` to ServerRoles (Android + iOS) and a per-operator
"To resolve names" toggle under the SMP section (xftp has no names
role; the shared ServerRoles field stays false there).
- Mirror the new agent error: NameErrorType + a NAME case on both
AgentErrorType and ProtocolErrorType (the SMP ErrorType mirror), so
the new SMP/agent NAME errors decode instead of crashing the decoder.
- Remove ChatErrorType.SimplexNameResolverUnavailable (deleted in core)
and repoint its "name resolution unavailable" alert to the agent
NAME NO_SERVERS error, reusing the existing strings.
Android (multiplatform) compiles clean; iOS mirrors the same changes
(builds in Xcode).
* feat(names): UI warning when no server resolves names
Mirror core USWNoNamesServers: add the NoNamesServers variant to
UserServersWarning (Kotlin sealed class + Swift enum) and its
globalWarning / globalServersWarning branch, rendered by the existing
ServersWarningFooter / ServersWarningView. Matches the noChatRelays
warning exactly.
* fix(servers): show all validation errors and warnings, not just the first
globalServersError/Warning returned only the first entry, so a second
warning (e.g. no names servers behind no chat relays) or a second error
(e.g. no XFTP servers behind no SMP servers) was never displayed. Make
them return all entries (globalServersErrors/Warnings) and render one
footer row each, across the three combined-footer views. Per-protocol
SMP/XFTP footers are unchanged.
* docs(names): add SimpleX name UI plan
* feat(names): add name model fields + SimplexName helpers
* feat(names): verify + set-name API & responses
* docs(names): bump core sync to 5008b4e62
* feat(names): show name + verification on chat info
* feat(names): add Verify SimpleX names privacy toggle
* feat(names): add set-name screens (user + channel)
* update ui
* fix kotlin
* fix codable
* fix ios
* fix errors
* api in UI
* send name as string in protocol
* update simplexmq, capitalize
* verify that name is in profile for own and known contacts and channels as condition of name resolution
* update simplexmq
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
parent
ec8fe669c7
commit
9bd38c4aec
@@ -1405,7 +1405,7 @@ data ChatError
|
||||
| ChatErrorRemoteHost {rhKey :: RHKey, remoteHostError :: RemoteHostError}
|
||||
deriving (Show, Exception)
|
||||
|
||||
-- why a resolved simplex name could not be used (the name itself resolved; an unregistered name is the agent's NAME NOT_FOUND)
|
||||
-- why a resolved SimpleX name could not be used (the name itself resolved; an unregistered name is the agent's NAME NOT_FOUND)
|
||||
data SimplexNameError
|
||||
= SNENoValidLink -- the name's record has no usable contact/channel link
|
||||
| SNEUnknownName -- the resolved link's profile has no name, or a different name
|
||||
|
||||
@@ -1507,7 +1507,7 @@ processChatCommand cxt nm = \case
|
||||
unless (nameResolvesTo sl nrSimplexContact) $ throwCmdError "name does not point to your address"
|
||||
pure $ Just (CLShort sl)
|
||||
_ -> throwCmdError "create the address short link and add it to name"
|
||||
let p' = (fromLocalProfile p :: Profile) {simplexName = (`SimplexNameClaim` Nothing) <$> name_, contactLink = cl'}
|
||||
let p' = (fromLocalProfile p :: Profile) {simplexName = mkSimplexNameClaim name_ Nothing, contactLink = cl'}
|
||||
updateProfile_ user p' True $ withFastStore $ \db -> do
|
||||
user' <- updateUserProfile db user p'
|
||||
liftIO $ setUserSimplexName db user' name_
|
||||
@@ -4207,17 +4207,30 @@ processChatCommand cxt nm = \case
|
||||
Nothing -> do
|
||||
l' <- resolveSLink
|
||||
(FixedLinkData {linkConnReq = cReq, rootKey}, cData) <- getShortLinkConnReq nm user l'
|
||||
contactSLinkData_ <- mapM linkDataBadge =<< liftIO (decodeLinkUserData cData)
|
||||
let linkProfile_ = (\ContactShortLinkData {profile} -> profile) <$> contactSLinkData_
|
||||
linkName_ = linkProfile_ >>= \Profile {simplexName} -> claimName <$> simplexName
|
||||
verifiedName_ = case nl of CTName ni -> Just ni; _ -> Nothing
|
||||
refreshContact ct' = case (verifiedName_, linkProfile_) of
|
||||
(Just _, Just p) -> updateContactFromLinkData user ct' p
|
||||
_ -> pure ct'
|
||||
forM_ verifiedName_ $ \ni -> verifyNameClaim ni linkName_
|
||||
withFastStore' (\db -> getContactWithoutConnViaShortAddress db cxt user l') >>= \case
|
||||
Just ct' | not (contactDeleted ct') -> pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct'))
|
||||
Just ct' | not (contactDeleted ct') -> do
|
||||
ct'' <- refreshContact ct'
|
||||
pure (con l' cReq, CPContactAddress (CAPContactViaAddress ct''))
|
||||
_ -> do
|
||||
contactSLinkData_ <- mapM linkDataBadge =<< liftIO (decodeLinkUserData cData)
|
||||
let ContactLinkData _ UserContactData {owners} = cData
|
||||
ov = verifyLinkOwner rootKey owners l' sig_
|
||||
plan <- contactRequestPlan user cReq contactSLinkData_ ov
|
||||
case (nl, plan) of
|
||||
(CTName ni, CPContactAddress cap@(CAPOk (Just ContactShortLinkData {profile = Profile {simplexName}}) _ _)) -> do
|
||||
_ <- verifyNameClaim ni (claimName <$> simplexName)
|
||||
pure (con l' cReq, CPContactAddress cap {verifiedName = Just ni})
|
||||
case plan of
|
||||
CPContactAddress cap@(CAPOk {}) -> pure (con l' cReq, CPContactAddress cap {verifiedName = verifiedName_})
|
||||
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''))
|
||||
_ -> pure (con l' cReq, plan)
|
||||
where
|
||||
knownLinkPlans = withFastStore $ \db ->
|
||||
@@ -4265,14 +4278,19 @@ processChatCommand cxt nm = \case
|
||||
(Nothing, Nothing) -> pure ()
|
||||
_ -> throwChatError CEInvalidConnReq
|
||||
let ov = verifyLinkOwner rootKey owners l' sig_
|
||||
verifiedName_ = case nl of CTName ni -> Just ni; _ -> Nothing
|
||||
claimedName GroupProfile {publicGroup} = claimName <$> (publicGroup >>= publicGroupAccess >>= publicGroupClaim)
|
||||
plan <- groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_ ov
|
||||
case (nl, plan) of
|
||||
(CTName ni, CPGroupLink glp@(GLPOk (Just _) (Just gld) _ _)) -> do
|
||||
let GroupShortLinkData {groupProfile = GroupProfile {publicGroup = pg}} = gld
|
||||
gName = claimName <$> (pg >>= publicGroupAccess >>= publicGroupClaim)
|
||||
_ <- verifyNameClaim ni gName
|
||||
pure (con l' cReq, CPGroupLink glp {verifiedName = Just ni})
|
||||
_ -> pure (con l' cReq, plan)
|
||||
forM_ verifiedName_ $ \ni ->
|
||||
verifyNameClaim ni $ case plan of
|
||||
CPGroupLink (GLPOk _ (Just GroupShortLinkData {groupProfile = gp}) _ _) -> claimedName gp
|
||||
CPGroupLink (GLPKnown GroupInfo {groupProfile = gp} _ _ _) -> claimedName gp
|
||||
CPGroupLink (GLPOwnLink GroupInfo {groupProfile = gp}) -> claimedName gp
|
||||
CPGroupLink (GLPConnectingProhibit (Just GroupInfo {groupProfile = gp})) -> claimedName gp
|
||||
_ -> maybe Nothing (\GroupShortLinkData {groupProfile = gp} -> claimedName gp) groupSLinkData_
|
||||
pure $ case plan of
|
||||
CPGroupLink glp@(GLPOk {}) -> (con l' cReq, CPGroupLink glp {verifiedName = verifiedName_})
|
||||
_ -> (con l' cReq, plan)
|
||||
where
|
||||
unsupportedGroupType = \case
|
||||
Just GroupShortLinkData {groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {groupType}}} -> groupType /= GTChannel
|
||||
|
||||
@@ -1467,6 +1467,19 @@ updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = G
|
||||
Just PublicGroupData {publicMemberCount} -> Just publicMemberCount /= localCount
|
||||
_ -> False
|
||||
|
||||
updateContactFromLinkData :: User -> Contact -> Profile -> CM Contact
|
||||
updateContactFromLinkData user ct@Contact {contactId, profile = profile@LocalProfile {simplexName = prevClaim, contactDomainVerification}} linkProfile@Profile {simplexName = newClaim}
|
||||
| profileChanged || verifyChanged = do
|
||||
cxt <- chatStoreCxt
|
||||
when profileChanged $ void $ withStore $ \db -> updateContactProfile db cxt user ct linkProfile
|
||||
when verifyChanged $ withStore' $ \db -> setContactDomainVerified db user contactId True
|
||||
withStore $ \db -> getContact db cxt user contactId
|
||||
| otherwise = pure ct
|
||||
where
|
||||
profileChanged = fromLocalProfile profile /= linkProfile
|
||||
claimChanged = (claimName <$> prevClaim) /= (claimName <$> newClaim)
|
||||
verifyChanged = contactDomainVerification /= Just True || claimChanged
|
||||
|
||||
-- TODO [relays] owner: set owners on updating link data (multi-owner)
|
||||
groupLinkData :: GroupInfo -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData)
|
||||
groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}, membership = GroupMember {memberId}, groupKeys} GroupLink {groupLinkId} groupRelays =
|
||||
|
||||
@@ -74,16 +74,16 @@ instance ToField NameClaimProof where toField = toField . encodeJSON
|
||||
instance FromField NameClaimProof where fromField = fromTextField_ decodeJSON
|
||||
|
||||
data SimplexNameClaim = SimplexNameClaim
|
||||
{ name :: SimplexNameInfo,
|
||||
{ name :: StrJSON "SimplexNameInfo" SimplexNameInfo,
|
||||
proof :: Maybe NameClaimProof
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
mkSimplexNameClaim :: Maybe SimplexNameInfo -> Maybe NameClaimProof -> Maybe SimplexNameClaim
|
||||
mkSimplexNameClaim name_ proof_ = (`SimplexNameClaim` proof_) <$> name_
|
||||
mkSimplexNameClaim name_ proof_ = (\n -> SimplexNameClaim (StrJSON n) proof_) <$> name_
|
||||
|
||||
claimName :: SimplexNameClaim -> SimplexNameInfo
|
||||
claimName (SimplexNameClaim n _) = n
|
||||
claimName (SimplexNameClaim n _) = unStrJSON n
|
||||
|
||||
claimProof :: SimplexNameClaim -> Maybe NameClaimProof
|
||||
claimProof (SimplexNameClaim _ p) = p
|
||||
|
||||
@@ -393,7 +393,7 @@ setUserSimplexName db user@User {userId, profile = p@LocalProfile {profileId}} n
|
||||
db
|
||||
"UPDATE contact_profiles SET simplex_name = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ?"
|
||||
(name_, ts, userId, profileId)
|
||||
pure (user :: User) {profile = p {simplexName = (`SimplexNameClaim` Nothing) <$> name_}}
|
||||
pure (user :: User) {profile = p {simplexName = mkSimplexNameClaim name_ Nothing}}
|
||||
|
||||
setUserProfileContactLink :: DB.Connection -> User -> Maybe UserContactLink -> IO User
|
||||
setUserProfileContactLink db user@User {userId, profile = p@LocalProfile {profileId}} ucl_ = do
|
||||
|
||||
+12
-13
@@ -1128,17 +1128,13 @@ simplexChatContact' = \case
|
||||
CLFull (CRContactUri crData) -> CLFull $ CRContactUri crData {crScheme = simplexChat}
|
||||
l@(CLShort _) -> l
|
||||
|
||||
shareLinkStr :: Maybe SimplexNameInfo -> B.ByteString -> B.ByteString
|
||||
shareLinkStr (Just ni) _ = strEncode ni
|
||||
shareLinkStr Nothing fallback = fallback
|
||||
|
||||
groupDomainName :: GroupInfo -> Maybe SimplexNameInfo
|
||||
groupDomainName GroupInfo {groupProfile = GroupProfile {publicGroup}} =
|
||||
claimName <$> (publicGroup >>= publicGroupAccess >>= publicGroupClaim)
|
||||
|
||||
viewNameVerified :: Maybe SimplexNameInfo -> Maybe Text -> [StyledString]
|
||||
viewNameVerified name_ result =
|
||||
let nameStr = maybe "name" (\ni -> "simplex name " <> shortNameInfoStr ni) name_
|
||||
let nameStr = maybe "name" (\ni -> "SimpleX name " <> shortNameInfoStr ni) name_
|
||||
in case result of
|
||||
Nothing -> [plain nameStr <> " verified"]
|
||||
Just reason -> [plain nameStr <> " not verified: " <> plain reason]
|
||||
@@ -1155,7 +1151,7 @@ simplexNameStatus (Just ni) status hasProof = case status of
|
||||
| hasProof -> [line "unverified"]
|
||||
| otherwise -> []
|
||||
where
|
||||
line s = "simplex name: " <> plain (shortNameInfoStr ni) <> " (" <> s <> ")"
|
||||
line s = "SimpleX name: " <> plain (shortNameInfoStr ni) <> " (" <> s <> ")"
|
||||
|
||||
-- TODO [short links] show all settings
|
||||
viewAddressSettings :: AddressSettings -> [StyledString]
|
||||
@@ -1174,12 +1170,14 @@ groupLink_ :: StyledString -> GroupInfo -> GroupLink -> [StyledString]
|
||||
groupLink_ intro g GroupLink {connLinkContact = CCLink cReq shortLink, acceptMemberRole} =
|
||||
[ intro,
|
||||
"",
|
||||
plain $ shareLinkStr (groupDomainName g) $ maybe cReqStr strEncode shortLink,
|
||||
"",
|
||||
"Anybody can connect to you and join group as " <> showRole acceptMemberRole <> " with: " <> highlight' "/c <group_link_above>",
|
||||
"to show it again: " <> highlight ("/show link #" <> viewGroupName g),
|
||||
"to delete it: " <> highlight ("/delete link #" <> viewGroupName g) <> " (joined members will remain connected to you)"
|
||||
plain $ maybe cReqStr strEncode shortLink
|
||||
]
|
||||
<> ["SimpleX name: " <> plain (shortNameInfoStr ni) | Just ni <- [groupDomainName g]]
|
||||
<> [ "",
|
||||
"Anybody can connect to you and join group as " <> showRole acceptMemberRole <> " with: " <> highlight' "/c <group_link_above>",
|
||||
"to show it again: " <> highlight ("/show link #" <> viewGroupName g),
|
||||
"to delete it: " <> highlight ("/delete link #" <> viewGroupName g) <> " (joined members will remain connected to you)"
|
||||
]
|
||||
<> ["The group link for old clients: " <> plain cReqStr | isJust shortLink]
|
||||
where
|
||||
cReqStr = strEncode $ simplexChatContact cReq
|
||||
@@ -1253,8 +1251,9 @@ viewGroupLinkRelaysUpdated g groupLink relays =
|
||||
<> map showRelay relays
|
||||
<>
|
||||
[ "group link:",
|
||||
plain $ shareLinkStr (groupDomainName g) $ maybe cReqStr strEncode shortLink
|
||||
plain $ maybe cReqStr strEncode shortLink
|
||||
]
|
||||
<> ["SimpleX name: " <> plain (shortNameInfoStr ni) | Just ni <- [groupDomainName g]]
|
||||
where
|
||||
GroupLink {connLinkContact = CCLink cReq shortLink} = groupLink
|
||||
cReqStr = strEncode $ simplexChatContact cReq
|
||||
@@ -2686,7 +2685,7 @@ viewChatError isCmd logLevel testView = \case
|
||||
let reason = case nameErr of
|
||||
SNENoValidLink -> "has no usable connection link"
|
||||
SNEUnknownName -> "is not included in the connection link's profile"
|
||||
in ["simplex name " <> plain (shortNameInfoStr ni) <> " " <> reason]
|
||||
in ["SimpleX name " <> plain (shortNameInfoStr ni) <> " " <> reason]
|
||||
CEUnsupportedConnReq -> [ "", "Connection link is not supported by the your app version, please ugrade it.", plain updateStr]
|
||||
CEInvalidChatMessage Connection {connId} msgMeta_ msg e ->
|
||||
[ plain $
|
||||
|
||||
Reference in New Issue
Block a user