diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index c909102c67..63f30b1e62 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1452,7 +1452,6 @@ struct ConnectionPlanResult { // APIConnectPlan resolution scope; .never is local-store-only (no network), used for per-keystroke name search enum PlanResolveMode: String { - case allGroups case unknown case never case all @@ -1488,15 +1487,6 @@ enum NameRegistration: Hashable { // the registry may add reasons after this version, so any other value is just "not registrable" static let reservedCommunity = "community" - enum CodingKeys: String, CodingKey { - case type - case expires - case graceUntil - case reservedReason_ - case pricing - case reservedReason - } - // a name past its expiry does not connect: only its owner can renew it until the grace ends func expired(_ now: Int64) -> Bool { if case let .registered(expires, _, _) = self, let expires { expires < now } else { false } @@ -1511,7 +1501,12 @@ enum NameRegistration: Hashable { } } +// stock derivation cannot read this: the core tags it flat as {"type": ...}, not in swift's nested shape extension NameRegistration: Decodable { + private enum CodingKeys: String, CodingKey { + case type, expires, graceUntil, reservedReason_, pricing, reservedReason + } + init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) let type = try c.decode(String.self, forKey: CodingKeys.type) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 735cf74628..b344ca2337 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -1500,50 +1500,8 @@ enum UIRemoteCtrlSessionState { case connected(remoteCtrl: RemoteCtrlInfo, sessionCode: String) } -// How recently each SimpleX name was resolved from the registry. -// -// The lookup canvas asks that a name you already have a chat for is resolved at most once a day, -// or once its registration has expired, while any other name resolves on every tap. Core stays -// stateless for this: .never answers from the store without a network round trip and reports a -// miss, and .all always resolves. So a fresh name is tried locally first and only falls through -// to the registry when no chat claims it - which is exactly "every tap" for a name you do not have. -enum NameResolution { - private static let daySeconds: Int64 = 24 * 60 * 60 - - private struct Resolved: Codable { - var at: Int64 - var expires: Int64? - } - - // when each SimpleX name was last resolved, so a name with a chat is not re-resolved on every tap - private static let resolvedAtDefault = CodableDefault<[String: Resolved]>(defaults: UserDefaults.standard, forKey: DEFAULT_SIMPLEX_NAMES_RESOLVED_AT, withDefault: [:]) - - private static func save(_ m: [String: Resolved]) { - // only names looked up in the last week are worth remembering - let now = nowSeconds() - resolvedAtDefault.set(m.filter { now - $0.value.at < 7 * daySeconds }) - } - - private static func nowSeconds() -> Int64 { Int64(Date.now.timeIntervalSince1970) } - - // a cached answer is stale a day after it was taken, or as soon as the name it described expired - static func isFresh(_ domain: SimplexDomain) -> Bool { - guard let r = resolvedAtDefault.get()[domain.fullDomainName] else { return false } - let now = nowSeconds() - if now - r.at >= daySeconds { return false } - return r.expires.map { now < $0 } ?? true - } - - static func record(_ domain: SimplexDomain, _ reg: NameRegistration?) { - var m = resolvedAtDefault.get() - let expires: Int64? = if case let .registered(expires, _, _) = reg { expires } else { nil } - m[domain.fullDomainName] = Resolved(at: nowSeconds(), expires: expires) - save(m) - } - - // a name registered, claimed or dropped on this device must not keep reading as it did before - static func forget(_ fullDomainName: String) { - var m = resolvedAtDefault.get() - if m.removeValue(forKey: fullDomainName) != nil { save(m) } - } +// when a SimpleX name was last resolved from the registry, and when that registration runs out +struct SimplexNameResolved: Codable { + var at: Int64 + var expires: Int64? } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 4066f4ef8d..2324fbd42a 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1390,8 +1390,6 @@ func showSetSimplexNameError(_ r: APIResult, isChannel: Bool) async { func apiSetUserDomain(_ simplexDomain: String?) async throws -> User { let userId = try currentUserId("apiSetUserDomain") let r: APIResult = await chatApiSendCmd(.apiSetUserDomain(userId: userId, simplexDomain: simplexDomain)) - // a name claimed or dropped here must not keep reading from the answer taken before - if let d = ChatModel.shared.currentUser?.profile.contactDomain?.domain { NameResolution.forget(d) } switch r { case let .result(.userProfileUpdated(user, _, _, _)): return user case let .result(.userProfileNoChange(user)): return user diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 411bf70103..9d88793a54 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -1324,6 +1324,38 @@ private func showOpenKnownGroupAlert( private let simplexNamesHowToURL = "https://simplex.domains/#testing" +private let nameResolvedDaySeconds: Int64 = 24 * 60 * 60 + +private func nameResolvedNow() -> Int64 { Int64(Date.now.timeIntervalSince1970) } + +private func saveNamesResolvedAt(_ m: [String: SimplexNameResolved]) { + // only names looked up in the last week are worth remembering + let now = nameResolvedNow() + simplexNamesResolvedAtDefault.set(m.filter { now - $0.value.at < 7 * nameResolvedDaySeconds }) +} + +// a cached answer is stale a day after it was taken, or as soon as the name it described expired +func simplexNameResolvedRecently(_ domain: SimplexDomain) -> Bool { + guard let r = simplexNamesResolvedAtDefault.get()[domain.fullDomainName] else { return false } + let now = nameResolvedNow() + if now - r.at >= nameResolvedDaySeconds { return false } + return r.expires.map { now < $0 } ?? true +} + +func recordSimplexNameResolved(_ domain: SimplexDomain, _ reg: NameRegistration?) { + var m = simplexNamesResolvedAtDefault.get() + let expires: Int64? = if case let .registered(expires, _, _) = reg { expires } else { nil } + m[domain.fullDomainName] = SimplexNameResolved(at: nameResolvedNow(), expires: expires) + saveNamesResolvedAt(m) +} + +// a name registered, claimed or dropped on this device must not keep reading as it did before +func forgetSimplexNameResolved(_ fullDomainName: String) { + var m = simplexNamesResolvedAtDefault.get() + if m.removeValue(forKey: fullDomainName) != nil { saveNamesResolvedAt(m) } +} + + private func nameDate(_ seconds: Int64) -> String { Date(timeIntervalSince1970: TimeInterval(seconds)).formatted(date: .abbreviated, time: .omitted) } @@ -1475,7 +1507,7 @@ func planAndConnect( // name with no chat gets on every tap. Anything that is not a name resolves as it always did. let nameTarget: SimplexNameInfo? = if case let .name(_, nameInfo) = strConnectTarget(shortOrFullLink) { nameInfo } else { nil } var result: ConnectionPlanResult? = nil - if let nameTarget, NameResolution.isFresh(nameTarget.nameDomain) { + if let nameTarget, simplexNameResolvedRecently(nameTarget.nameDomain) { // a local probe: no error alerts, so a miss falls through silently result = await apiConnectPlan(connLink: shortOrFullLink, resolveMode: .never, linkOwnerSig: linkOwnerSig, inProgress: BoxedValue(false)) } @@ -1483,7 +1515,7 @@ func planAndConnect( result = await apiConnectPlan(connLink: shortOrFullLink, resolveMode: nameTarget != nil ? .all : .unknown, linkOwnerSig: linkOwnerSig, inProgress: inProgress) // remember when this name was last taken from the registry, and when its registration runs out if let nameTarget, let result { - NameResolution.record(nameTarget.nameDomain, result.connectionPlan.nameRegistration) + recordSimplexNameResolved(nameTarget.nameDomain, result.connectionPlan.nameRegistration) } } await MainActor.run { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 93aa8ceb6c..9e8a28434a 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -291,6 +291,8 @@ public class CodableDefault { let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def) +let simplexNamesResolvedAtDefault = CodableDefault<[String: SimplexNameResolved]>(defaults: UserDefaults.standard, forKey: DEFAULT_SIMPLEX_NAMES_RESOLVED_AT, withDefault: [:]) + struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 9711f3d6f8..93c127459c 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -205,6 +205,8 @@ struct UserAddressView: View { save: { simplexDomain in do { let u = try await apiSetUserDomain(simplexDomain) + // a name claimed or dropped here must not keep reading from the answer taken before + if let d = chatModel.currentUser?.profile.contactDomain?.domain { forgetSimplexNameResolved(d) } await MainActor.run { chatModel.updateUser(u) } return true } catch { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 242c002e11..8d458a49d0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -5252,55 +5252,9 @@ enum class SimplexTLD { @SerialName("web") web } -// How recently each SimpleX name was resolved: one with a local chat resolves once a day, any other on every tap. -object NameResolution { - private const val DAY_SECONDS = 24 * 60 * 60L - private val prefs: AppPreferences get() = ChatController.appPrefs - - @Serializable - private data class Resolved(val at: Long, val expires: Long? = null) - - private fun load(): MutableMap = - try { - val s = prefs.simplexNamesResolvedAt.get() ?: return mutableMapOf() - json.decodeFromString>(s).toMutableMap() - } catch (e: Exception) { - mutableMapOf() - } - - private fun save(m: Map) { - // only names looked up in the last week are worth remembering - val now = nowSeconds() - val kept = m.filterValues { now - it.at < 7 * DAY_SECONDS } - try { - prefs.simplexNamesResolvedAt.set(json.encodeToString>(kept)) - } catch (e: Exception) { - Log.e(TAG, "NameResolution.save: ${e.stackTraceToString()}") - } - } - - private fun nowSeconds(): Long = Clock.System.now().epochSeconds - - // a cached answer is stale a day after it was taken, or as soon as the name it described expired - fun isFresh(domain: SimplexDomain): Boolean { - val r = load()[domain.fullDomainName] ?: return false - val now = nowSeconds() - if (now - r.at >= DAY_SECONDS) return false - return r.expires == null || now < r.expires - } - - fun record(domain: SimplexDomain, reg: NameRegistration?) { - val m = load() - m[domain.fullDomainName] = Resolved(nowSeconds(), (reg as? NameRegistration.Registered)?.expires) - save(m) - } - - // a name registered, claimed or dropped on this device must not keep reading as it did before - fun forget(fullDomainName: String) { - val m = load() - if (m.remove(fullDomainName) != null) save(m) - } -} +// when a SimpleX name was last resolved from the registry, and when that registration runs out +@Serializable +data class SimplexNameResolved(val at: Long, val expires: Long? = null) // What the registry holds for a name - the RNAME payload, "type"-tagged on every platform. @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index d6ec13ef34..d4127dd59c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1944,8 +1944,6 @@ object ChatController { suspend fun apiSetUserDomain(rh: Long?, simplexDomain: String?): User { val userId = currentUserId("apiSetUserDomain") val r = sendCmd(rh, CC.ApiSetUserDomain(userId, simplexDomain)) - // a name claimed or dropped here must not keep reading from the answer taken before - chatModel.currentUser.value?.profile?.contactDomain?.let { NameResolution.forget(it.domain) } return when { r is API.Result && r.res is CR.UserProfileUpdated -> r.res.user.updateRemoteHostId(rh) r is API.Result && r.res is CR.UserProfileNoChange -> r.res.user.updateRemoteHostId(rh) @@ -7435,9 +7433,8 @@ data class ConnectionPlanResult( // APIConnectPlan resolution scope; PRMNever is local-store-only (no network), used for per-keystroke name search enum class PlanResolveMode { - PRMAllGroups, PRMUnknown, PRMNever, PRMAll; + PRMUnknown, PRMNever, PRMAll; val cmdString: String get() = when (this) { - PRMAllGroups -> "allGroups" PRMUnknown -> "unknown" PRMNever -> "never" PRMAll -> "all" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 35f2c2d4a2..7d775cddf5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -22,6 +22,7 @@ import chat.simplex.common.views.usersettings.simplexTeamUri import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.datetime.* +import kotlinx.serialization.* enum class ConnectionLinkType { INVITATION, CONTACT, GROUP @@ -74,6 +75,48 @@ private fun openNameHowTo(uriHandler: UriHandler) = openBrowserAlert(SIMPLEX_NAM private const val SIMPLEX_NAMES_HOWTO_URL = "https://simplex.domains/#testing" +private const val NAME_RESOLVED_DAY_SECONDS = 24 * 60 * 60L + +private fun loadNamesResolvedAt(): MutableMap = + try { + val s = ChatController.appPrefs.simplexNamesResolvedAt.get() ?: return mutableMapOf() + json.decodeFromString>(s).toMutableMap() + } catch (e: Exception) { + mutableMapOf() + } + +private fun saveNamesResolvedAt(m: Map) { + // only names looked up in the last week are worth remembering + val now = Clock.System.now().epochSeconds + val kept = m.filterValues { now - it.at < 7 * NAME_RESOLVED_DAY_SECONDS } + try { + ChatController.appPrefs.simplexNamesResolvedAt.set(json.encodeToString>(kept)) + } catch (e: Exception) { + Log.e(TAG, "saveNamesResolvedAt: ${e.stackTraceToString()}") + } +} + +// a cached answer is stale a day after it was taken, or as soon as the name it described expired +fun simplexNameResolvedRecently(domain: SimplexDomain): Boolean { + val r = loadNamesResolvedAt()[domain.fullDomainName] ?: return false + val now = Clock.System.now().epochSeconds + if (now - r.at >= NAME_RESOLVED_DAY_SECONDS) return false + return r.expires == null || now < r.expires +} + +fun recordSimplexNameResolved(domain: SimplexDomain, reg: NameRegistration?) { + val m = loadNamesResolvedAt() + m[domain.fullDomainName] = SimplexNameResolved(Clock.System.now().epochSeconds, (reg as? NameRegistration.Registered)?.expires) + saveNamesResolvedAt(m) +} + +// a name registered, claimed or dropped on this device must not keep reading as it did before +fun forgetSimplexNameResolved(fullDomainName: String) { + val m = loadNamesResolvedAt() + if (m.remove(fullDomainName) != null) saveNamesResolvedAt(m) +} + + // what the registry said, when the user can act on it; false leaves the caller to show its usual plan UI. private fun showNameRegistrationAlert( rhId: Long?, @@ -209,7 +252,7 @@ private suspend fun planAndConnectTask( } // A fresh name is tried against the store first; a miss falls through to a full resolution. val nameTarget = (strConnectTarget(shortOrFullLink.trim()) as? ConnectTarget.Name)?.nameInfo - val freshName = nameTarget != null && NameResolution.isFresh(nameTarget.nameDomain) + val freshName = nameTarget != null && simplexNameResolvedRecently(nameTarget.nameDomain) var result = if (freshName) { // a local probe: no spinner and no error alerts, so a miss falls through silently chatModel.controller.apiConnectPlan(rhId, shortOrFullLink, PlanResolveMode.PRMNever, linkOwnerSig, mutableStateOf(false)) @@ -219,7 +262,7 @@ private suspend fun planAndConnectTask( result = chatModel.controller.apiConnectPlan(rhId, shortOrFullLink, mode, linkOwnerSig, inProgress) // remember when this name was last taken from the registry, and when its registration runs out if (nameTarget != null && result != null) { - NameResolution.record(nameTarget.nameDomain, result.connectionPlan.nameRegistration()) + recordSimplexNameResolved(nameTarget.nameDomain, result.connectionPlan.nameRegistration()) } } connectProgressManager.stopConnectProgress() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 87660c7e65..a063b64032 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -388,6 +388,8 @@ private fun UserAddressLayout( save = { simplexDomain -> try { val u = chatModel.controller.apiSetUserDomain(user?.remoteHostId, simplexDomain) + // a name claimed or dropped here must not keep reading from the answer taken before + domain?.let { forgetSimplexNameResolved(it) } withContext(Dispatchers.Main) { chatModel.updateUser(u) } true } catch (e: Exception) { diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 1ceb8c5a1f..cdb6ff8db5 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -543,7 +543,7 @@ directoryServiceEvent opts@DirectoryOpts {adminUsers, superUsers, serviceName, o <> ".\nIt is hidden from the directory until approved." notifyAdminUsers $ "The " <> gt <> " " <> groupRef <> " is updated" <> byMember <> "." verifyAndSendToApprove g' gr' n' - sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAllGroups Nothing) >>= \case + sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAll Nothing) >>= \case Right (CRConnectionPlan _ _ _ _ (CPGroupLink (GLPKnown {groupInfo = g'}) _)) -> case dbOwnerMemberId gr of Just ownerGMId -> @@ -776,7 +776,7 @@ directoryServiceEvent opts@DirectoryOpts {adminUsers, superUsers, serviceName, o forM_ pg_ $ \pg@PublicGroupProfile {groupLink} -> when (groupRegStatus == GRSActive || pendingApproval groupRegStatus) $ do let link = ACL SCMContact $ CLShort groupLink - sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAllGroups Nothing) >>= \case + sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAll Nothing) >>= \case Right (CRConnectionPlan _ _ _ _ (CPGroupLink (GLPKnown {groupInfo = g', groupUpdated, linkOwners = ListDef owners}) _)) -> checkValidOwner dbOwnerMemberId owners $ do -- re-verify every cycle: a name that stopped resolving to the link must lose verified status @@ -914,7 +914,7 @@ directoryServiceEvent opts@DirectoryOpts {adminUsers, superUsers, serviceName, o let link = ACL SCMContact $ CLShort connLink mId = MemberId oIdBytes gt' = groupTypeStr gt - sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAllGroups (Just ownerSig)) >>= \case + sendChatCmd cc (APIConnectPlan userId (Just (aConnectTarget link)) PRMAll (Just ownerSig)) >>= \case Right (CRConnectionPlan _ (Just (ACCL SCMContact ccLink)) _ _ plan) -> handleGroupLinkPlan ct ccLink mId ownerSig gt' plan _ -> sendMessage cc ct "Error: could not connect. Please report it to directory admins." diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 48058923c6..f4e95ff885 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -709,8 +709,7 @@ data ChatCommand deriving (Show) data PlanResolveMode - = PRMAllGroups -- resolve all known groups and all unknown chats - | PRMUnknown -- only resolve if chat is unknown (default) + = PRMUnknown -- only resolve if chat is unknown (default) | PRMNever -- do not resolve links and names, only do local search | PRMAll -- always resolve, also known chats deriving (Eq, Show) @@ -718,8 +717,6 @@ data PlanResolveMode planResolveModeP :: A.Parser PlanResolveMode planResolveModeP = A.takeTill (== ' ') >>= \case - "allGroups" -> pure PRMAllGroups - "on" -> pure PRMAllGroups "unknown" -> pure PRMUnknown "off" -> pure PRMUnknown "never" -> pure PRMNever diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index c4e727390b..c0d3aacbcc 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -4528,8 +4528,8 @@ processChatCommand cxt nm = \case CTLink l' -> pure l' CTName n -> serverShortLink <$> resolveNameLink n con l' cReq = ACCL SCMContact $ CCLink cReq (Just l') - -- a name whose chat is known is re-resolved only on PRMAll, to see whether it still leads there - reResolveKnown (_, p) = resolveMode == PRMAll && isJust simplexName_ && case p of + -- a known chat is re-resolved only on PRMAll, to see whether it still leads there + reResolveKnown (_, p) = resolveMode == PRMAll && case p of CPContactAddress (CAPKnown _) _ -> True CPGroupLink GLPKnown {} _ -> True _ -> False @@ -4541,14 +4541,12 @@ processChatCommand cxt nm = \case groupShortLinkPlan :: CM (ACreatedConnLink, ConnectionPlan) groupShortLinkPlan = knownLinkPlans >>= \case - Just (_, CPGroupLink (GLPKnown g _ _ _) _) - | resolveMode == PRMAllGroups -> resolveSLink >>= \l' -> resolveKnownGroup l' g Just r | not (reResolveKnown r) -> pure r known_ -> do when (resolveMode == PRMNever) $ throwChatError CENotResolvedLocally l' <- resolveSLink case known_ of - -- the name still leads to the channel that claims it, refreshed as PRMAllGroups does + -- the name still leads to the channel that claims it, refreshed from the link Just r@(_, CPGroupLink (GLPKnown g _ _ _) _) | knownLinkOf r == Just l' -> resolveKnownGroup l' g _ -> (if isJust known_ then second setAddressChanged else id) <$> resolvedGroupPlan l' where diff --git a/tests/ChatTests/Names.hs b/tests/ChatTests/Names.hs index d64e06962a..ea3c2ef7a5 100644 --- a/tests/ChatTests/Names.hs +++ b/tests/ChatTests/Names.hs @@ -155,7 +155,7 @@ testChannelDomainLinkJoinUnverified ps = withSmpServerAndNames $ \reg -> cath <## "updated public group access: domain=team.simplex" memberJoinChannel "team" [cath] [alice] shortLink fullLink bob -- a link-data refresh must not mark the self-claimed name verified - bob ##> ("/_connect plan 1 " <> shortLink <> " resolve=allGroups") + bob ##> ("/_connect plan 1 " <> shortLink <> " resolve=all") bob <## "group link: known group #team" bob <## "use #team to send messages" -- no "SimpleX name" line: status stays unknown where @@ -184,7 +184,7 @@ testChannelDomainVerify ps = withSmpServerAndNames $ \reg -> bob ##> "/_verify domain #1" bob <## "SimpleX name #team not verified: the name does not resolve to the link in the group profile" -- a link-data refresh keeps the failed status, not overwritten with verified - bob ##> ("/_connect plan 1 " <> shortLink <> " resolve=allGroups") + bob ##> ("/_connect plan 1 " <> shortLink <> " resolve=all") bob <## "group link: known group #team" bob <## "SimpleX name: #team (verification failed)" bob <## "use #team to send messages"