mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
feat: render NIP-42 relay logins (kind 22242) as a login card, not a post
Kind 22242 had no NoteCompose renderer, so signer consent prompts (NIP-46 bunker, napplets) previewed an auth request as an empty text note. - commonsUI: RelayAuthCard shows "Relay login", a "nothing is posted" explainer, each relay (icon + URL) and the challenge; wired into the NoteCompose and ThreadFeedView dispatchers. - Consent: the op label reads "log in to relays as you" instead of "sign for Relay Auth (kind: 22242)", and the preview line names the relay(s) instead of the empty content (batched rows were blank). - quartz: TagArray.authRelays()/authChallenge() + RelayAuthEvent.relays(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GSwjzewTFcwzqAKVJ1WJ9B
This commit is contained in:
+21
-4
@@ -24,8 +24,12 @@ import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAut
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer.Companion.toNarrowSignerOp
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerOp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.authRelays
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -91,10 +95,7 @@ object Nip46ConsentInfoBuilder {
|
||||
|
||||
val preview =
|
||||
when {
|
||||
request is BunkerRequestSign ->
|
||||
request.event.content
|
||||
.take(PREVIEW_MAX_CHARS)
|
||||
.trim()
|
||||
request is BunkerRequestSign -> signPreview(request.event.kind, request.event.tags, request.event.content)
|
||||
plaintext != null -> plaintext.take(PREVIEW_MAX_CHARS).trim()
|
||||
else -> ""
|
||||
}
|
||||
@@ -132,6 +133,22 @@ object Nip46ConsentInfoBuilder {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The one-line preview of an event an app asks to sign. Usually its content; a NIP-42 relay
|
||||
* login has none, so it names the relay(s) instead — otherwise a batched row reads as a blank
|
||||
* "sign Relay Auth" with nothing to say where the user is logging in.
|
||||
*/
|
||||
fun signPreview(
|
||||
kind: Int,
|
||||
tags: TagArray,
|
||||
content: String,
|
||||
): String =
|
||||
if (kind == RelayAuthEvent.KIND) {
|
||||
tags.authRelays().joinToString(", ") { it.displayUrl() }
|
||||
} else {
|
||||
content.take(PREVIEW_MAX_CHARS).trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the message the app asked to read. Never throws and never hangs: a signer that fails,
|
||||
* refuses, returns nothing, or takes too long yields [failureText], because a request whose
|
||||
|
||||
@@ -35,10 +35,12 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_fallback_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_decrypt
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_decrypt_from
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_encrypt
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_relay_login
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_sign_kind_named
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_allow_always_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.kindNameFor
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.Nip46ConsentInfoBuilder
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConnectInfo
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConsentInfo
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
@@ -46,12 +48,19 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/** Human-readable label for a [NostrSignerOp]. */
|
||||
suspend fun NostrSignerOp.label(context: Context): String =
|
||||
when (this) {
|
||||
is NostrSignerOp.SignKind -> loadStringRes(Res.string.napplet_op_sign_kind_named, kindNameFor(kind), kind)
|
||||
is NostrSignerOp.SignKind ->
|
||||
if (kind == RelayAuthEvent.KIND) {
|
||||
// A relay login is not "signing" anything the user would recognise; say what it does.
|
||||
loadStringRes(Res.string.napplet_op_relay_login)
|
||||
} else {
|
||||
loadStringRes(Res.string.napplet_op_sign_kind_named, kindNameFor(kind), kind)
|
||||
}
|
||||
NostrSignerOp.Encrypt -> loadStringRes(Res.string.napplet_op_encrypt)
|
||||
NostrSignerOp.Decrypt -> loadStringRes(Res.string.napplet_op_decrypt)
|
||||
is NostrSignerOp.DecryptFrom -> loadStringRes(Res.string.napplet_op_decrypt_from, counterpartyLabel(counterparty))
|
||||
@@ -96,8 +105,8 @@ suspend fun buildSignerConsentInfo(
|
||||
val summary = (narrowOp ?: op).label(context)
|
||||
val preview =
|
||||
when (request) {
|
||||
is NappletRequest.Publish -> request.content.take(160).trim()
|
||||
is NappletRequest.SignEvent -> request.content.take(160).trim()
|
||||
is NappletRequest.Publish -> Nip46ConsentInfoBuilder.signPreview(request.kind, request.tags, request.content)
|
||||
is NappletRequest.SignEvent -> Nip46ConsentInfoBuilder.signPreview(request.kind, request.tags, request.content)
|
||||
is NappletRequest.PublishEncrypted -> request.content.take(160).trim()
|
||||
// Encryption shows the plaintext the page wants sealed; decryption has only ciphertext,
|
||||
// which tells the user nothing, so its preview stays empty and the counterparty in
|
||||
|
||||
+8
-1
@@ -105,6 +105,7 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_decision_deny
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_decrypt
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_decrypt_from
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_encrypt
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_relay_login
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_sign_kind
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_permissions_ask_each_time
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_policy_full_trust
|
||||
@@ -141,6 +142,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.nip46AppOnli
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.nip46ClientSubtitle
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -838,7 +840,12 @@ private fun GrantOption(
|
||||
@Composable
|
||||
private fun NostrSignerOp.opLabel(): String =
|
||||
when (this) {
|
||||
is NostrSignerOp.SignKind -> stringRes(Res.string.napplet_op_sign_kind, kind)
|
||||
is NostrSignerOp.SignKind ->
|
||||
if (kind == RelayAuthEvent.KIND) {
|
||||
stringRes(Res.string.napplet_op_relay_login)
|
||||
} else {
|
||||
stringRes(Res.string.napplet_op_sign_kind, kind)
|
||||
}
|
||||
NostrSignerOp.Encrypt -> stringRes(Res.string.napplet_op_encrypt)
|
||||
NostrSignerOp.Decrypt -> stringRes(Res.string.napplet_op_decrypt)
|
||||
is NostrSignerOp.DecryptFrom -> stringRes(Res.string.napplet_op_decrypt_from, counterpartyLabel(counterparty))
|
||||
|
||||
+22
@@ -22,7 +22,9 @@ package com.vitorpamplona.amethyst.connectedApps.consent
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerOp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Decrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt
|
||||
@@ -173,4 +175,24 @@ class Nip46ConsentInfoBuilderTest {
|
||||
assertNull(info.narrowOp)
|
||||
assertNotNull(info.previewTemplate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRelayLoginPreviewNamesTheRelayInsteadOfItsEmptyContent() =
|
||||
runTest {
|
||||
val auth = RelayAuthEvent.build(RelayUrlNormalizer.normalize("wss://relay.damus.io"), "chal-123")
|
||||
val template = EventTemplate<Event>(auth.createdAt, auth.kind, auth.tags, auth.content)
|
||||
val info = build(BunkerRequestSign(event = template), op = NostrSignerOp.SignKind(RelayAuthEvent.KIND))
|
||||
|
||||
assertEquals("relay.damus.io", info.contentPreview)
|
||||
assertEquals(template, info.previewTemplate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRegularPostPreviewIsStillItsContent() =
|
||||
runTest {
|
||||
val template = EventTemplate<Event>(1L, 1, emptyArray(), " hello world ")
|
||||
val info = build(BunkerRequestSign(event = template), op = NostrSignerOp.SignKind(1))
|
||||
|
||||
assertEquals("hello world", info.contentPreview)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,6 +1134,10 @@
|
||||
<string name="relay_auth_session_section">Just for now</string>
|
||||
<string name="relay_auth_session_row_desc">Logged in until you restart Amethyst</string>
|
||||
<string name="relay_auth_forget_session">Forget this login</string>
|
||||
<string name="relay_auth_event_title">Relay login</string>
|
||||
<string name="relay_auth_event_explainer">Proves to the relay that you control this account. Nothing is posted.</string>
|
||||
<string name="relay_auth_event_no_relay">No relay named in this login</string>
|
||||
<string name="relay_auth_event_challenge">Challenge: %1$s</string>
|
||||
<string name="relay_auth_session_forgotten_undo">%1$s will ask again the next time it needs you.</string>
|
||||
<string name="relay_auth_session_undo_blocked">Not restored. “Never log in” is on, so Amethyst won't log in to %1$s.</string>
|
||||
<string name="relay_auth_blocked_section">Blocked by your block list</string>
|
||||
@@ -4505,6 +4509,7 @@
|
||||
<string name="napplet_op_decrypt">read your private messages</string>
|
||||
<string name="napplet_op_decrypt_from">read your private messages with %1$s</string>
|
||||
<string name="napplet_op_encrypt">encrypt a message</string>
|
||||
<string name="napplet_op_relay_login">log in to relays as you</string>
|
||||
<string name="napplet_op_sign_kind_named">sign for %1$s (kind: %2$d)</string>
|
||||
<string name="napplet_permissions_title">Connected Apps</string>
|
||||
<string name="napplets">nApplets</string>
|
||||
|
||||
+6
@@ -221,6 +221,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderPublicationIndex
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderPublicationSection
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderReaction
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayAddMember
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayAuth
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayDiscovery
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayGroupMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayJoinRequest
|
||||
@@ -372,6 +373,7 @@ import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.addMember.RelayAddMemberEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.joinRequest.RelayJoinRequestEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.leaveRequest.RelayLeaveRequestEvent
|
||||
@@ -1307,6 +1309,10 @@ private fun RenderNoteRow(
|
||||
RenderRelayLeaveRequest(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is RelayAuthEvent -> {
|
||||
RenderRelayAuth(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is PinListEvent -> {
|
||||
RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note.types
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_event_challenge
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_event_explainer
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_event_no_relay
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_event_title
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RenderRelay
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
|
||||
/**
|
||||
* NIP-42 kind 22242: a login to a relay. It is not a post — its content is empty and nobody but
|
||||
* the relay ever sees it — so it renders as "log in to <relay>" instead of falling through to the
|
||||
* text-note layout. Mostly met in signer consent prompts, where an app asks to sign one.
|
||||
*/
|
||||
@Composable
|
||||
fun RenderRelayAuth(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? RelayAuthEvent ?: return
|
||||
val relays = remember(noteEvent) { noteEvent.relays() }
|
||||
val challenge = remember(noteEvent) { noteEvent.challenge() }
|
||||
|
||||
RelayAuthCard(relays, challenge) { relay ->
|
||||
RenderRelay(relay, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RelayAuthCard(
|
||||
relays: List<NormalizedRelayUrl>,
|
||||
challenge: String?,
|
||||
relayIcon: @Composable (NormalizedRelayUrl) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.Key,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = stringRes(Res.string.relay_auth_event_title),
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(Res.string.relay_auth_event_explainer),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (relays.isEmpty()) {
|
||||
Text(
|
||||
text = stringRes(Res.string.relay_auth_event_no_relay),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(start = 32.dp),
|
||||
)
|
||||
}
|
||||
|
||||
relays.forEach { relay ->
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 32.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
relayIcon(relay)
|
||||
Text(
|
||||
text = relay.displayUrl(),
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
challenge?.let {
|
||||
Text(
|
||||
text = stringRes(Res.string.relay_auth_event_challenge, it),
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontFamily = FontFamily.Monospace),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(start = 32.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RelayAuthCardPreview() {
|
||||
ThemeComparisonColumn {
|
||||
RelayAuthCard(
|
||||
relays = listOf(NormalizedRelayUrl("wss://relay.damus.io/")),
|
||||
challenge = "4f2c9a1e-7b3d-4c8e-a6f0-2d9b1e3c5a7f",
|
||||
) { relay ->
|
||||
RenderRelayIcon(
|
||||
displayUrl = relay.url,
|
||||
iconUrl = null,
|
||||
loadProfilePicture = false,
|
||||
loadRobohash = true,
|
||||
pingInMs = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -223,6 +223,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderPublicMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderPublicationSection
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderReaction
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayAddMember
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayAuth
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayDiscovery
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayJoinRequest
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.types.RenderRelayLeaveRequest
|
||||
@@ -341,6 +342,7 @@ import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.addMember.RelayAddMemberEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.joinRequest.RelayJoinRequestEvent
|
||||
import com.vitorpamplona.quartz.nip43RelayMembers.leaveRequest.RelayLeaveRequestEvent
|
||||
@@ -1119,6 +1121,8 @@ private fun FullBleedNoteCompose(
|
||||
RenderRelayJoinRequest(baseNote, accountViewModel, nav)
|
||||
} else if (noteEvent is RelayLeaveRequestEvent) {
|
||||
RenderRelayLeaveRequest(baseNote, accountViewModel, nav)
|
||||
} else if (noteEvent is RelayAuthEvent) {
|
||||
RenderRelayAuth(baseNote, accountViewModel, nav)
|
||||
} else if (noteEvent is TextNoteModificationEvent) {
|
||||
RenderTextModificationEvent(
|
||||
note = baseNote,
|
||||
|
||||
+3
-1
@@ -42,7 +42,9 @@ class RelayAuthEvent(
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun relay() = tags.firstNotNullOfOrNull(RelayTag::parse)
|
||||
|
||||
fun challenge() = tags.firstNotNullOfOrNull(ChallengeTag::parse)
|
||||
fun relays() = tags.authRelays()
|
||||
|
||||
fun challenge() = tags.authChallenge()
|
||||
|
||||
companion object {
|
||||
const val KIND = 22242
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip42RelayAuth
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.fastFirstNotNullOfOrNull
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.tags.ChallengeTag
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.tags.RelayTag
|
||||
|
||||
/** Every relay a NIP-42 auth event (or its unsigned template) logs into. Usually just one. */
|
||||
fun TagArray.authRelays() = mapNotNull(RelayTag::parse)
|
||||
|
||||
fun TagArray.authChallenge() = fastFirstNotNullOfOrNull(ChallengeTag::parse)
|
||||
Reference in New Issue
Block a user