Merge 842b8f81f1d1687ae629c97cdc916696cf1be885 into 1f24973b06ee77492742ba522002e028f38f4381

This commit is contained in:
Taksh Kothari 2026-11-08 12:36:46 +05:30 committed by GitHub
commit ecdb082dbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 8 deletions

View File

@ -97,12 +97,31 @@ object RelayDirectory {
}
val (lat, lon) = center
return snapshot
.asSequence()
.sortedBy { haversineMeters(lat, lon, it.latitude, it.longitude) }
.take(nRelays.coerceAtLeast(0))
.map { it.url }
.toList()
return selectClosest(snapshot, lat, lon, nRelays)
}
/**
* Same distance order as iOS, with the host as the tie-break, after
* `host` and `host:443` have been collapsed to one URL.
*/
internal fun selectClosest(
entries: List<RelayInfo>,
lat: Double,
lon: Double,
count: Int
): List<String> {
if (entries.isEmpty() || count <= 0) return emptyList()
val seen = LinkedHashSet<String>()
entries
.map { it.copy(url = canonicalRelayUrl(it.url)) }
.sortedWith(
compareBy<RelayInfo> { haversineMeters(lat, lon, it.latitude, it.longitude) }
.thenBy { relaySortKey(it.url) }
)
.forEach { entry ->
if (entry.url.isNotEmpty()) seen.add(entry.url)
}
return seen.take(count).toList()
}
private fun haversineMeters(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
@ -114,12 +133,28 @@ object RelayDirectory {
return R * c
}
private fun normalizeRelayUrl(raw: String): String {
internal fun canonicalRelayUrl(raw: String): String {
val trimmed = raw.trim()
if (trimmed.isEmpty()) return trimmed
return if ("://" in trimmed) trimmed else "wss://$trimmed"
val withScheme = if ("://" in trimmed) trimmed else "wss://$trimmed"
val schemeEnd = withScheme.indexOf("://")
if (schemeEnd < 0) return withScheme
val scheme = withScheme.substring(0, schemeEnd).lowercase()
val rest = withScheme.substring(schemeEnd + 3)
val authority = rest.substringBefore('/').substringBefore('?').substringBefore('#')
val tail = rest.removePrefix(authority)
val host = authority.substringBefore(':').lowercase()
if (host.isEmpty()) return withScheme
val port = authority.substringAfter(':', "")
val authorityOut = if (port.isEmpty() || port == "443") host else "$host:$port"
return "$scheme://$authorityOut$tail"
}
private fun relaySortKey(url: String): String =
url.substringAfter("://", url).substringBefore('/').substringBefore(':')
private fun normalizeRelayUrl(raw: String): String = canonicalRelayUrl(raw)
// ===== Implementation details =====
private fun getPrefs(application: Application): SharedPreferences =

View File

@ -0,0 +1,37 @@
package com.bitchat.android.nostr
import org.junit.Assert.assertEquals
import org.junit.Test
class RelayDirectorySelectionTest {
@Test
fun `canonical url drops the default port and lowercases the host`() {
assertEquals(
"wss://relay.example.com",
RelayDirectory.canonicalRelayUrl("relay.example.com:443")
)
assertEquals(
"wss://relay.example.com",
RelayDirectory.canonicalRelayUrl("WSS://Relay.Example.com:443")
)
assertEquals(
"wss://relay.example.com:8443",
RelayDirectory.canonicalRelayUrl("relay.example.com:8443")
)
}
@Test
fun `duplicate host and port do not take two of the five slots`() {
val entries = listOf(
RelayDirectory.RelayInfo("wss://b.example", 0.0, 0.0),
RelayDirectory.RelayInfo("b.example:443", 0.0, 0.0),
RelayDirectory.RelayInfo("a.example", 0.0, 0.0),
RelayDirectory.RelayInfo("c.example", 1.0, 1.0)
)
val picked = RelayDirectory.selectClosest(entries, lat = 0.0, lon = 0.0, count = 2)
assertEquals(listOf("wss://a.example", "wss://b.example"), picked)
}
}