fix(nests): use moq-auth.nostrnests.com for JWT mint

The previous defaults pointed both `service` and `endpoint` URLs at
`moq.nostrnests.com:4443`. That host only listens on UDP/QUIC for the
WebTransport relay — TCP :4443 is unreachable. OkHttp can't speak HTTP/3,
so the JWT-mint POST hangs and fails with `ConnectException`, leaving
the room screen stuck on "Reconnecting".

The real nostrnests deployment co-locates auth and relay on different
hosts (verified against the production NestsUI bundle):

    relay (WebTransport, QUIC only):  https://moq.nostrnests.com:4443
    auth  (JWT mint, regular HTTPS):  https://moq-auth.nostrnests.com

Split the defaults accordingly and add a `resolveServerPair` helper that
maps a single saved kind-10112 URL onto the (service, endpoint) pair the
kind-30312 event needs. The helper recognises both the auth-host and
the legacy relay-host the prior defaults wrote, so users upgrading from
the broken version migrate transparently the next time they open the
create-room sheet. Community deployments that genuinely co-locate keep
working via the fallback.

Update the recommended-servers list and the kind-10112 documentation to
match: the stored `server` URL is the moq-auth base (the URL that ends
up in the kind-30312 `service` tag).
This commit is contained in:
Claude
2026-04-29 18:20:21 +00:00
parent 47f700afb3
commit 63555db48a
4 changed files with 59 additions and 12 deletions
@@ -247,8 +247,13 @@ private fun NestsServerEntry(
* Built-in suggestion list shown under "Recommended servers". Today
* just the public `nostrnests.com` deployment; add new entries here
* as community-run moq-rs / moq-auth instances come online.
*
* The stored URL is the moq-auth (service / JWT mint) base — that's
* the URL that ends up in the kind-30312 `service` tag. The matching
* WebTransport relay endpoint is resolved by
* [com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create.CreateNestViewModel.resolveServerPair].
*/
val DEFAULT_NESTS_SERVERS: List<NestsServer> =
listOf(
NestsServer(name = "nostrnests.com", baseUrl = "https://moq.nostrnests.com:4443"),
NestsServer(name = "nostrnests.com", baseUrl = "https://moq-auth.nostrnests.com"),
)
@@ -115,7 +115,11 @@ class NestsServersViewModel : ViewModel() {
private fun displayHostName(url: String): String =
try {
Rfc3986.host(url).removePrefix("moq.").removePrefix("nests.")
Rfc3986
.host(url)
.removePrefix("moq-auth.")
.removePrefix("moq.")
.removePrefix("nests.")
} catch (_: Throwable) {
url
}
@@ -124,7 +128,10 @@ class NestsServersViewModel : ViewModel() {
/**
* Display-friendly entry for [NestsServersViewModel].
* - [name] — short host label (e.g. `nostrnests.com`)
* - [baseUrl] — exact bytes that go on the wire (`https://moq.nostrnests.com`)
* - [baseUrl] — exact bytes that go on the wire as the kind-30312
* `service` tag (`https://moq-auth.nostrnests.com`). The matching
* WebTransport endpoint is resolved at room-create time, not
* stored here.
*/
data class NestsServer(
val name: String,
@@ -79,7 +79,8 @@ class CreateNestViewModel : ViewModel() {
.firstOrNull()
?.takeIf { it.startsWith("http") }
if (savedFirst != null) {
_state.update { it.copy(serviceUrl = savedFirst, endpointUrl = savedFirst) }
val (service, endpoint) = resolveServerPair(savedFirst)
_state.update { it.copy(serviceUrl = service, endpointUrl = endpoint) }
}
}
@@ -355,8 +356,38 @@ class CreateNestViewModel : ViewModel() {
* Public nostrnests deployment — a blank form produces a working
* room here. Users can edit either field to point at their own
* moq-auth / moq-relay pair.
*
* The auth sidecar (moq-auth) and the WebTransport relay
* (moq-rs) are on different hosts: moq-auth speaks regular
* HTTPS on :443 to mint JWTs; moq.nostrnests.com:4443 is the
* QUIC/WebTransport endpoint and does NOT accept TCP. Don't
* collapse them back into a single URL — OkHttp can't speak
* HTTP/3, so a TCP POST to :4443 will hang and fail.
*/
const val DEFAULT_SERVICE_URL: String = "https://moq.nostrnests.com:4443"
const val DEFAULT_SERVICE_URL: String = "https://moq-auth.nostrnests.com"
const val DEFAULT_ENDPOINT_URL: String = "https://moq.nostrnests.com:4443"
/**
* Map a single saved kind-10112 URL onto the (service, endpoint)
* pair the kind-30312 event needs. Recognises the public
* nostrnests deployment (whether the user has the auth host or
* the relay host saved — earlier app versions stored the relay
* URL by mistake), and falls back to using the URL for both
* tags so community-run deployments that genuinely co-locate
* keep working.
*/
internal fun resolveServerPair(savedUrl: String): Pair<String, String> =
when (savedUrl.trimEnd('/')) {
"https://moq-auth.nostrnests.com",
"https://moq.nostrnests.com:4443",
"https://moq.nostrnests.com",
-> {
DEFAULT_SERVICE_URL to DEFAULT_ENDPOINT_URL
}
else -> {
savedUrl to savedUrl
}
}
}
}
@@ -42,19 +42,23 @@ import com.vitorpamplona.quartz.utils.TimeUtils
* "kind": 10112,
* "tags": [
* ["alt", "Audio-room (nests) MoQ servers used by the author"],
* ["server", "https://moq.nostrnests.com"],
* ["server", "https://moq-auth.nostrnests.com"],
* ["server", "https://moq.example.org"],
* ...
* ],
* "content": ""
* }
*
* The `server` URL points at the moq-rs / moq-auth deployment's base
* URL — Amethyst's `CreateNestSheet` uses it for both the
* `service` (auth sidecar) and `endpoint` (WebTransport relay) tags
* on the kind-30312 event, since nostrnests's reference deployment
* co-locates them. A future revision can split the two into separate
* tag fields if the community pulls them apart.
* The `server` URL is the moq-auth (JWT mint) base — that's also the
* URL that goes into the kind-30312 `service` tag. The matching
* WebTransport relay endpoint is NOT part of this list; reference
* deployments like nostrnests run the auth sidecar on regular HTTPS
* (e.g. `https://moq-auth.nostrnests.com`) and the QUIC relay on a
* separate host/port (e.g. `https://moq.nostrnests.com:4443`), so
* Amethyst's `CreateNestSheet` resolves the pair via a known-deployment
* mapping (with a fallback that reuses the saved URL for both tags
* when a community deployment genuinely co-locates them). A future
* revision can split the two into separate tag fields if needed.
*
* This event is shaped 1:1 after `BlossomServersEvent` (kind 10063,
* NIP-B7) so existing list-state / settings UI patterns translate