mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 01:18:11 +00:00
align app divergences
This commit is contained in:
@@ -1426,14 +1426,6 @@ private func showNameWarningAlert(
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor private func nameChatId(_ chatInfo: ChatInfo?) -> ChatId? {
|
||||
guard let chatInfo else { return nil }
|
||||
if ChatModel.shared.getChat(chatInfo.id) == nil {
|
||||
ChatModel.shared.addChat(Chat(chatInfo: chatInfo, chatItems: []))
|
||||
}
|
||||
return chatInfo.id
|
||||
}
|
||||
|
||||
private func showOtherNameAlert(_ otherSimplexName: SimplexNameInfo, connectOtherButton: String, theme: AppTheme, dismiss: Bool, cleanup: (() -> Void)?) {
|
||||
showAlert(
|
||||
String.localizedStringWithFormat(
|
||||
@@ -1610,7 +1602,6 @@ func planAndConnect(
|
||||
case let .ok(contactSLinkData_, ownerVerification, addressChanged, existingChat_):
|
||||
if let contactSLinkData = contactSLinkData_ {
|
||||
logger.debug("planAndConnect, .contactAddress, .ok, short link data present")
|
||||
let existingChatId = await nameChatId(filterKnownContact == nil ? existingChat_ : nil)
|
||||
await MainActor.run {
|
||||
showPrepareContactAlert(
|
||||
connectionLink: connectionLink,
|
||||
@@ -1620,7 +1611,7 @@ func planAndConnect(
|
||||
connectOtherButton: connectOtherButton,
|
||||
connectOtherLink: connectOtherLink,
|
||||
addressChanged: addressChanged,
|
||||
openExistingChat: existingChatId.map { chatId in { openKnownChat(chatId, dismiss: dismiss, cleanup: cleanup) } },
|
||||
openExistingChat: (filterKnownContact == nil ? existingChat_ : nil).map { chatInfo in { openKnownChat(chatInfo.id, dismiss: dismiss, cleanup: cleanup) } },
|
||||
theme: theme,
|
||||
dismiss: dismiss,
|
||||
cleanup: cleanup
|
||||
@@ -1704,7 +1695,6 @@ func planAndConnect(
|
||||
case let .ok(groupShortLinkInfo_, groupSLinkData_, ownerVerification, addressChanged, existingChat_):
|
||||
if let groupSLinkData = groupSLinkData_ {
|
||||
logger.debug("planAndConnect, .groupLink, .ok, short link data present")
|
||||
let existingChatId = await nameChatId(filterKnownGroup == nil ? existingChat_ : nil)
|
||||
await MainActor.run {
|
||||
showPrepareGroupAlert(
|
||||
connectionLink: connectionLink,
|
||||
@@ -1715,7 +1705,7 @@ func planAndConnect(
|
||||
connectOtherButton: connectOtherButton,
|
||||
connectOtherLink: connectOtherLink,
|
||||
addressChanged: addressChanged,
|
||||
openExistingChat: existingChatId.map { chatId in { openKnownChat(chatId, dismiss: dismiss, cleanup: cleanup) } },
|
||||
openExistingChat: (filterKnownGroup == nil ? existingChat_ : nil).map { chatInfo in { openKnownChat(chatInfo.id, dismiss: dismiss, cleanup: cleanup) } },
|
||||
theme: theme,
|
||||
dismiss: dismiss,
|
||||
cleanup: cleanup
|
||||
@@ -1739,16 +1729,20 @@ func planAndConnect(
|
||||
await MainActor.run {
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
if let otherSimplexName = result.otherSimplexName, let connectOtherButton {
|
||||
showOtherNameAlert(otherSimplexName, connectOtherButton: connectOtherButton, theme: theme, dismiss: dismiss, cleanup: cleanup)
|
||||
}
|
||||
} else {
|
||||
showOwnGroupLinkConfirmConnectSheet(
|
||||
groupInfo: groupInfo,
|
||||
connectionLink: connectionLink,
|
||||
connectionPlan: connectionPlan,
|
||||
connectOtherButton: connectOtherButton,
|
||||
onConnectOther: connectOtherLink.map { link in { planAndConnect(link, theme: theme, dismiss: dismiss, cleanup: cleanup) } },
|
||||
dismiss: dismiss,
|
||||
cleanup: cleanup
|
||||
)
|
||||
}
|
||||
showOwnGroupLinkConfirmConnectSheet(
|
||||
groupInfo: groupInfo,
|
||||
connectionLink: connectionLink,
|
||||
connectionPlan: connectionPlan,
|
||||
connectOtherButton: connectOtherButton,
|
||||
onConnectOther: connectOtherLink.map { link in { planAndConnect(link, theme: theme, dismiss: dismiss, cleanup: cleanup) } },
|
||||
dismiss: dismiss,
|
||||
cleanup: cleanup
|
||||
)
|
||||
}
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect")
|
||||
|
||||
+3
-5
@@ -290,7 +290,7 @@ private suspend fun planAndConnectTask(
|
||||
connectOtherButton = connectOtherButton,
|
||||
connectOtherLink = connectOtherLink,
|
||||
addressChanged = connectionPlan.contactAddressPlan.addressChanged,
|
||||
openExistingChat = if (filterKnownContact == null) localNameChat(rhId, connectionPlan.contactAddressPlan.existingChat_)?.let { chat -> { openChat_(chatModel, rhId, close, chat); cleanup() } } else null,
|
||||
openExistingChat = if (filterKnownContact == null) connectionPlan.contactAddressPlan.existingChat_?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } } else null,
|
||||
close,
|
||||
cleanup
|
||||
)
|
||||
@@ -388,7 +388,7 @@ private suspend fun planAndConnectTask(
|
||||
connectOtherButton = connectOtherButton,
|
||||
connectOtherLink = connectOtherLink,
|
||||
addressChanged = connectionPlan.groupLinkPlan.addressChanged,
|
||||
openExistingChat = if (filterKnownGroup == null) localNameChat(rhId, connectionPlan.groupLinkPlan.existingChat_)?.let { chat -> { openChat_(chatModel, rhId, close, chat); cleanup() } } else null,
|
||||
openExistingChat = if (filterKnownGroup == null) connectionPlan.groupLinkPlan.existingChat_?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } } else null,
|
||||
close,
|
||||
cleanup
|
||||
)
|
||||
@@ -410,6 +410,7 @@ private suspend fun planAndConnectTask(
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
if (filterKnownGroup != null) {
|
||||
filterKnownGroup(groupInfo)
|
||||
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup)
|
||||
} else {
|
||||
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, connectionPlan, groupInfo, close, cleanup, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
|
||||
}
|
||||
@@ -888,9 +889,6 @@ fun showPrepareContactAlert(
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun localNameChat(rhId: Long?, chatInfo: ChatInfo?): Chat? =
|
||||
chatInfo?.let { chatModel.getChat(it.id) ?: Chat(remoteHostId = rhId, chatInfo = it, chatItems = emptyList()).also { chat -> chatModel.chatsContext.addChat(chat) } }
|
||||
|
||||
private fun showOtherNameAlert(rhId: Long?, otherSimplexName: SimplexNameInfo, connectOtherButton: String, close: (() -> Unit)?, cleanup: (() -> Unit)?) {
|
||||
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
|
||||
title = String.format(
|
||||
|
||||
@@ -181,7 +181,7 @@ Review on 2026-09-24 reversed decision 6. Each decision below was taken by the a
|
||||
|
||||
1. **Storage.** `contact_profiles.contact_domain_resolved_at` and `contact_domain_expires_at`; `groups.group_domain_resolved_at` and `group_domain_expires_at` — beside `contact_domain_verified` and `group_domain_verified`, which record whether the name checked out; these record when it was last resolved and when its registration expires. `TEXT` in SQLite, `TIMESTAMPTZ` in Postgres, `_at` as in `badge_purchases.expires_at`. One migration per backend.
|
||||
2. **Rule.** Under `PRMUnknown`, a known chat reached by a name is re-resolved when `resolved_at` is `NULL` or over a day old, or `expires_at` has passed. `PRMAll` always resolves; `PRMNever` never does. A name with no chat is resolved on every call and nothing is stored; the user's own address is not a chat, so it too is resolved on every call.
|
||||
3. **Writes.** Wherever core sets a verification flag after a resolution, it also sets `resolved_at` to now and `expires_at` to the registration's expiry, or `NULL` when the caller does not have it — then only the one-day limit applies until the next resolution. That is `setContactDomainVerified`, `setGroupDomainVerified`, and `createPreparedContact`, which inserts a chat created by name already verified. The plan passes the expiry of the registration it resolved, and `updateGroupFromLinkData` takes it from its callers, since it must not resolve; `/_verify domain` and `APISetPublicGroupAccess` pass `NULL`. Two paths gain a write: a re-resolution confirming the name still resolves to the known chat, and one that answers the chat without a warning because the name no longer has a link of its kind or is not registered (`plans/2026-09-28-name-warnings.md`, N7 and N11). Both re-set the flag to `True`, a no-op, since only verified chats are found by name. Setting the flag to `True` clears it on the user's other chats of the name's kind (contacts and business chats for a contact name, channels for a channel name), so after "Open new chat" in 3c the name finds the new chat.
|
||||
3. **Writes.** Wherever core sets a verification flag after a resolution, it also sets `resolved_at` to now and `expires_at` to the registration's expiry, or `NULL` when the caller does not have it — then only the one-day limit applies until the next resolution. That is `setContactDomainVerified`, `setGroupDomainVerified`, and `createPreparedContact`, which inserts a chat created by name already verified, only when the link's profile claims the name the app passes. The plan passes the expiry of the registration it resolved, and `updateGroupFromLinkData` takes it from its callers, since it must not resolve; `/_verify domain` and `APISetPublicGroupAccess` pass `NULL`. Two paths gain a write: a re-resolution confirming the name still resolves to the known chat, and one that answers the chat without a warning because the name no longer has a link of its kind or is not registered (`plans/2026-09-28-name-warnings.md`, N7 and N11). Both re-set the flag to `True`, a no-op, since only verified chats are found by name. Setting the flag to `True` clears it on the user's other chats of the name's kind (contacts and business chats for a contact name, channels for a channel name), so after "Open new chat" in 3c the name finds the new chat.
|
||||
4. **Moved name.** When re-resolution finds the name resolves elsewhere (3c), nothing is written to the old chat. It stays stale, so each default lookup re-resolves and reports the new address; `resolve=never` still returns the old chat.
|
||||
5. **Reading.** A store function reads the two columns for the one chat being planned. `LocalProfile` and `GroupInfo` do not change, so the columns never reach the UIs; loading them there would touch 19 queries in 6 store files and both types' JSON.
|
||||
6. **UIs.** Both apps lose the cache — the preference, `SimplexNameResolved`, the local probe before resolving, and the invalidation in `UserAddressView` — and plan a name with the default mode.
|
||||
|
||||
@@ -242,7 +242,7 @@ A missing `graceUntil` drops the second clause of the expiry lines. `otherSimple
|
||||
|
||||
- **2a, 3c, 4a:** the other kind's button is shown for bare names only.
|
||||
- **3c:** also applies to the own address and channel. From a message, it shows Open new chat and Open existing chat, with no Cancel.
|
||||
- **3e (new):** a bare name matches a chat, and the name also leads to the other kind: "bakery.simplex also leads to channel #bakery.simplex", Join channel, OK. From a message it also shows Open existing chat.
|
||||
- **3e (new):** a bare name matches a chat or own channel, and the name also leads to the other kind: "bakery.simplex also leads to channel #bakery.simplex", Join channel, OK. From a message it also shows Open existing chat.
|
||||
- **Prices:** "$X for 2 years", computed from the registry's price. The amounts on the canvas are examples.
|
||||
- **3a:** unchanged: "Still leads to your chat, or not found, no valid link, another name, or the request failed" matches §3.
|
||||
|
||||
|
||||
@@ -2181,6 +2181,8 @@ processChatCommand cxt nm = \case
|
||||
APIConnectPlan _ Nothing _ _ -> throwChatError CEInvalidConnReq
|
||||
APIPrepareContact userId accLink verifiedDomain contactSLinkData -> withUserId userId $ \user -> do
|
||||
let ContactShortLinkData {profile, message, business} = contactSLinkData
|
||||
Profile {contactDomain} = profile
|
||||
domain_ = if verifiedDomain == (claimDomain <$> contactDomain) then verifiedDomain else Nothing
|
||||
welcomeSharedMsgId <- forM message $ \_ -> getSharedMsgId
|
||||
case accLink of
|
||||
ACCL SCMContact ccLink
|
||||
@@ -2189,7 +2191,7 @@ 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_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing domain_
|
||||
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
|
||||
@@ -2202,7 +2204,7 @@ processChatCommand cxt nm = \case
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
|
||||
ACCL _ (CCLink cReq _) -> do
|
||||
ct <- withStore $ \db -> createPreparedContact db cxt user profile accLink welcomeSharedMsgId (True <$ verifiedDomain)
|
||||
ct <- withStore $ \db -> createPreparedContact db cxt user profile accLink welcomeSharedMsgId (True <$ domain_)
|
||||
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
|
||||
@@ -2215,9 +2217,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 = GroupProfile {description, publicGroup}} = groupSLinkData
|
||||
domain_ = if verifiedDomain == (claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) 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
|
||||
|
||||
@@ -34,6 +34,7 @@ chatNamesTests :: SpecWith TestParams
|
||||
chatNamesTests = do
|
||||
it "connect by resolved name" testConnectByName
|
||||
it "connect by name not claimed in link profile is rejected" testConnectByNameNotClaimed
|
||||
it "prepare with a name not claimed in link profile is not verified" testPrepareNameNotClaimed
|
||||
it "connect by name to a known contact not claimed in profile is rejected" testConnectByNameKnownContactNotClaimed
|
||||
it "connect by unregistered name reports it is available" testConnectByNameNotFound
|
||||
it "set name not resolving to own address is rejected" testSetNameNotOwnAddress
|
||||
@@ -447,6 +448,25 @@ setContactNamesStale cc = withCCTransaction cc $ \db -> DB.execute_ db "UPDATE c
|
||||
setGroupNamesStale :: TestCC -> IO ()
|
||||
setGroupNamesStale cc = withCCTransaction cc $ \db -> DB.execute_ db "UPDATE groups SET group_domain_resolved_at = datetime('now', '-2 days')"
|
||||
|
||||
testPrepareNameNotClaimed :: HasCallStack => TestParams -> IO ()
|
||||
testPrepareNameNotClaimed ps = withSmpServerAndNames $ \reg ->
|
||||
testChat2 aliceProfile bobProfile (test reg) ps
|
||||
where
|
||||
test reg alice bob = do
|
||||
mapM_ enableNamesRole [alice, bob]
|
||||
alice ##> "/ad"
|
||||
(shortLink, fullLink) <- getContactLinks alice True
|
||||
registerName reg aliceSimplexName (contactNameRecord "alice.simplex" (T.pack shortLink))
|
||||
alice ##> "/_set domain 1 alice.simplex"
|
||||
alice <## "new contact address set"
|
||||
bob ##> ("/_connect plan 1 " <> shortLink)
|
||||
bob <## "contact address: ok to connect"
|
||||
contactSLinkData <- getTermLine bob
|
||||
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " domain=bob.simplex " <> contactSLinkData)
|
||||
bob <## "alice: contact is prepared"
|
||||
bob ##> "/_connect plan 1 @alice.simplex resolve=never"
|
||||
bob <## "no matching chat found, name resolution is disabled"
|
||||
|
||||
testPlanNameReservedOther :: HasCallStack => TestParams -> IO ()
|
||||
testPlanNameReservedOther = withAliceName $ \reg _r _alice bob -> do
|
||||
registerReservedName reg acmeName NRRTrademark
|
||||
|
||||
Reference in New Issue
Block a user