Send feedback to the relays advertised on the Amber repo announcement

The feedback issue was published to a hardcoded nos.lol with a synthetic
repository-event hint. sendFeedBack now resolves the developer's NIP-65
relay list first, fetches the NIP-34 repository announcement (kind 30617,
d=Amber) from it, and publishes the issue to the relays in its relays
tag, using the real announcement as the issue's hint. When either fetch
fails it falls back to the previous nos.lol behavior.

On Tor both the fetch window (30s -> 60s) and the publish confirmation
timeout (15s -> 60s) are widened, since Tor round trips routinely exceed
the old limits.
This commit is contained in:
greenart7c3
2026-09-25 07:40:50 -03:00
parent cec366b1e3
commit 473cfdd325
@@ -60,16 +60,20 @@ import com.greenart7c3.nostrsigner.service.ZapstoreUpdater
import com.greenart7c3.nostrsigner.service.crashreports.CrashReportCache
import com.greenart7c3.nostrsigner.service.crashreports.UnexpectedCrashSaver
import com.greenart7c3.nostrsigner.ui.ToastManager
import com.vitorpamplona.quartz.nip01Core.crypto.verify
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchFirst
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.publishAndConfirm
import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthenticator
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import java.lang.ref.WeakReference
import java.net.InetSocketAddress
@@ -782,18 +786,18 @@ class Amber :
type: FeedbackType,
account: Account,
): Boolean {
val relays = setOfNotNull(
RelayUrlNormalizer.normalizeOrNull("wss://nos.lol/"),
)
val usingTor = settings.torMode != TorMode.DISABLED
// Tor round trips are slow; give the relays time to answer OK before
// declaring the publish failed.
val publishTimeoutSeconds =
if (usingTor) FEEDBACK_TOR_PUBLISH_TIMEOUT_SECONDS else FEEDBACK_PUBLISH_TIMEOUT_SECONDS
val repositoryEvent = GitRepositoryEvent(
"",
DEVELOPER_HEX_KEY,
TimeUtils.now(),
tags = arrayOf(arrayOf("d", "Amber")),
"",
"",
)
val repositoryEvent = fetchAmberRepositoryEvent(usingTor) ?: fallbackRepositoryEvent()
val relays = repositoryEvent.relays()
.mapNotNull { RelayUrlNormalizer.normalizeOrNull(it) }
.toSet()
.takeIf { it.isNotEmpty() }
?: setOfNotNull(RelayUrlNormalizer.normalizeOrNull(FEEDBACK_FALLBACK_RELAY))
val template = GitIssueEvent.build(
subject,
@@ -810,9 +814,88 @@ class Amber :
// This will automatically connect to these relays even if they are not in the list
// once the events have been saved, it will disconnect from them unless there are other
// filters with them
return client.publishAndConfirm(event, relays)
return client.publishAndConfirm(event, relays, publishTimeoutSeconds)
}
/**
* NIP-34: a repository announcement's `relays` tag lists the relays its
* issues live on. The announcement itself is published on the author's own
* relays, so resolve those from their NIP-65 relay list first, then ask
* for the announcement. Returns null when either step finds nothing.
*/
private suspend fun fetchAmberRepositoryEvent(usingTor: Boolean): GitRepositoryEvent? {
val fetchTimeoutMs = if (usingTor) FEEDBACK_TOR_FETCH_TIMEOUT_MS else FEEDBACK_FETCH_TIMEOUT_MS
val authorRelays = fetchAuthorRelays(fetchTimeoutMs)
val discoveryRelays = (authorRelays + settings.defaultRelays)
.takeIf { it.isNotEmpty() }
?: setOfNotNull(RelayUrlNormalizer.normalizeOrNull(FEEDBACK_FALLBACK_RELAY))
val event = client.fetchFirst(
filters = discoveryRelays.associateWith {
listOf(
Filter(
kinds = listOf(GitRepositoryEvent.KIND),
authors = listOf(DEVELOPER_HEX_KEY),
tags = mapOf("d" to listOf(AMBER_REPO_IDENTIFIER)),
limit = 1,
),
)
},
idleTimeoutMs = fetchTimeoutMs,
) ?: return null
if (!event.verify()) return null
return GitRepositoryEvent(
event.id,
event.pubKey,
event.createdAt,
event.tags,
event.content,
event.sig,
)
}
private suspend fun fetchAuthorRelays(fetchTimeoutMs: Long): Set<NormalizedRelayUrl> {
val discoveryRelays = (settings.defaultProfileRelays + settings.defaultRelays)
.toSet()
.takeIf { it.isNotEmpty() }
?: setOfNotNull(RelayUrlNormalizer.normalizeOrNull(FEEDBACK_FALLBACK_RELAY))
val event = client.fetchFirst(
filters = discoveryRelays.associateWith {
listOf(
Filter(
kinds = listOf(AdvertisedRelayListEvent.KIND),
authors = listOf(DEVELOPER_HEX_KEY),
limit = 1,
),
)
},
idleTimeoutMs = fetchTimeoutMs,
) ?: return emptySet()
if (!event.verify()) return emptySet()
return AdvertisedRelayListEvent(
event.id,
event.pubKey,
event.createdAt,
event.tags,
event.content,
event.sig,
).relaysNorm().toSet()
}
/** Stand-in used for the issue's `a` tag hint when no announcement was found. */
private fun fallbackRepositoryEvent(): GitRepositoryEvent = GitRepositoryEvent(
"",
DEVELOPER_HEX_KEY,
TimeUtils.now(),
tags = arrayOf(arrayOf("d", AMBER_REPO_IDENTIFIER)),
"",
"",
)
companion object {
var isAppInForeground = false
const val TAG = "Amber"
@@ -821,6 +904,12 @@ class Amber :
private set
const val DEVELOPER_HEX_KEY = "7579076d9aff0a4cfdefa7e2045f2486c7e5d8bc63bfc6b45397233e1bbfcb19"
private const val AMBER_REPO_IDENTIFIER = "Amber"
private const val FEEDBACK_FALLBACK_RELAY = "wss://nos.lol/"
private const val FEEDBACK_PUBLISH_TIMEOUT_SECONDS = 15L
private const val FEEDBACK_TOR_PUBLISH_TIMEOUT_SECONDS = 60L
private const val FEEDBACK_FETCH_TIMEOUT_MS = 30_000L
private const val FEEDBACK_TOR_FETCH_TIMEOUT_MS = 60_000L
}
}