ui: prefer selecting relays from different operators when creating channel (#6668)

This commit is contained in:
spaced4ndy
2026-03-27 10:31:12 +00:00
committed by GitHub
parent 5e9c84fa21
commit 80eb678892
2 changed files with 72 additions and 11 deletions

View File

@@ -165,7 +165,7 @@ struct AddChannelView: View {
creationInProgress = true
Task {
do {
let enabledRelays = try await getEnabledRelays()
let enabledRelays = try await chooseRandomRelays()
let relayIds = enabledRelays.compactMap { $0.chatRelayId }
guard !relayIds.isEmpty else {
await MainActor.run {
@@ -201,13 +201,44 @@ struct AddChannelView: View {
}
}
// TODO [relays] move random relay selection to backend; prefer selecting relays from different operators
private func getEnabledRelays() async throws -> [UserChatRelay] {
private let maxRelays = 3
private func chooseRandomRelays() async throws -> [UserChatRelay] {
let servers = try await getUserServers()
let all = servers.flatMap { op in
op.chatRelays.filter { $0.enabled && !$0.deleted && $0.chatRelayId != nil }
// Operator relays are grouped per operator; custom relays (nil operator)
// are treated independently to maximize trust distribution.
var operatorGroups: [[UserChatRelay]] = []
var customRelays: [UserChatRelay] = []
for op in servers {
let relays = op.chatRelays.filter { $0.enabled && !$0.deleted && $0.chatRelayId != nil }
guard !relays.isEmpty else { continue }
if op.operator != nil {
operatorGroups.append(relays.shuffled())
} else {
customRelays = relays.shuffled()
}
}
return Array(all.shuffled().prefix(3))
var selected: [UserChatRelay] = []
// Prefer at least one custom relay when available -
// user's own infrastructure for trust distribution.
if let relay = customRelays.first {
selected.append(relay)
customRelays.removeFirst()
if selected.count >= maxRelays { return selected }
}
// Round-robin across shuffled groups to distribute relays across operators.
var groups = operatorGroups + customRelays.map { [$0] }
groups.shuffle()
let maxDepth = groups.map(\.count).max() ?? 0
for depth in 0..<maxDepth {
for group in groups {
if depth < group.count {
selected.append(group[depth])
if selected.count >= maxRelays { return selected }
}
}
}
return selected
}
private func checkHasRelays() async -> Bool {

View File

@@ -115,7 +115,7 @@ fun AddChannelView(chatModel: ChatModel, close: () -> Unit, closeAll: () -> Unit
creationInProgress.value = true
withBGApi {
try {
val enabledRelays = getEnabledRelays()
val enabledRelays = chooseRandomRelays()
val relayIds = enabledRelays.mapNotNull { it.chatRelayId }
if (relayIds.isEmpty()) {
withContext(Dispatchers.Main) {
@@ -159,12 +159,42 @@ fun AddChannelView(chatModel: ChatModel, close: () -> Unit, closeAll: () -> Unit
}
}
private suspend fun getEnabledRelays(): List<UserChatRelay> {
private const val maxRelays = 3
private suspend fun chooseRandomRelays(): List<UserChatRelay> {
val servers = getUserServers(rh = null) ?: return emptyList()
val all = servers.flatMap { op ->
op.chatRelays.filter { it.enabled && !it.deleted && it.chatRelayId != null }
// Operator relays are grouped per operator; custom relays (null operator)
// are treated independently to maximize trust distribution.
val operatorGroups = mutableListOf<List<UserChatRelay>>()
var customRelays = mutableListOf<UserChatRelay>()
for (op in servers) {
val relays = op.chatRelays.filter { it.enabled && !it.deleted && it.chatRelayId != null }
if (relays.isEmpty()) continue
if (op.operator != null) {
operatorGroups.add(relays.shuffled())
} else {
customRelays = relays.shuffled().toMutableList()
}
}
return all.shuffled().take(3)
val selected = mutableListOf<UserChatRelay>()
// Prefer at least one custom relay when available -
// user's own infrastructure for trust distribution.
if (customRelays.isNotEmpty()) {
selected.add(customRelays.removeAt(0))
if (selected.size >= maxRelays) return selected
}
// Round-robin across shuffled groups to distribute relays across operators.
val groups = (operatorGroups + customRelays.map { listOf(it) }).shuffled()
val maxDepth = groups.maxOfOrNull { it.size } ?: 0
for (depth in 0 until maxDepth) {
for (group in groups) {
if (depth < group.size) {
selected.add(group[depth])
if (selected.size >= maxRelays) return selected
}
}
}
return selected
}
private suspend fun checkHasRelays(): Boolean {