dynamically adding relay hints to nip19 codes.

This commit is contained in:
fiatjaf 2023-04-25 21:51:43 -03:00
parent b6207ae104
commit d912a28987
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
3 changed files with 101 additions and 42 deletions

View File

@ -22,7 +22,7 @@ object Components {
entry("canonical hex", bytes32.toHex), entry("canonical hex", bytes32.toHex),
"if this is a public key:", "if this is a public key:",
div( div(
cls := "pl-2 mb-2", cls := "mt-2 pl-2 mb-2",
entry( entry(
"npub", "npub",
NIP19.encode(XOnlyPublicKey(bytes32)) NIP19.encode(XOnlyPublicKey(bytes32))
@ -66,14 +66,13 @@ object Components {
) )
def renderEventPointer( def renderEventPointer(
store: Store,
evp: snow.EventPointer evp: snow.EventPointer
): Resource[IO, HtmlDivElement[IO]] = ): Resource[IO, HtmlDivElement[IO]] =
div( div(
cls := "text-md", cls := "text-md",
entry("event id (hex)", evp.id), entry("event id (hex)", evp.id),
if evp.relays.size > 0 then relayHints(store, evp.relays),
Some(entry("relay hints", evp.relays.reduce((a, b) => s"$a, $b")))
else None,
evp.author.map { pk => evp.author.map { pk =>
entry("author hint (pubkey hex)", pk.value.toHex) entry("author hint (pubkey hex)", pk.value.toHex)
}, },
@ -82,6 +81,7 @@ object Components {
) )
def renderProfilePointer( def renderProfilePointer(
store: Store,
pp: snow.ProfilePointer, pp: snow.ProfilePointer,
sk: Option[PrivateKey] = None sk: Option[PrivateKey] = None
): Resource[IO, HtmlDivElement[IO]] = ): Resource[IO, HtmlDivElement[IO]] =
@ -90,14 +90,13 @@ object Components {
sk.map { k => entry("private key (hex)", k.value.toHex) }, sk.map { k => entry("private key (hex)", k.value.toHex) },
sk.map { k => entry("nsec", NIP19.encode(k)) }, sk.map { k => entry("nsec", NIP19.encode(k)) },
entry("public key (hex)", pp.pubkey.value.toHex), entry("public key (hex)", pp.pubkey.value.toHex),
if pp.relays.size > 0 then relayHints(store, pp.relays),
Some(entry("relay hints", pp.relays.reduce((a, b) => s"$a, $b")))
else None,
entry("npub", NIP19.encode(pp.pubkey)), entry("npub", NIP19.encode(pp.pubkey)),
nip19_21("nprofile", NIP19.encode(pp)) nip19_21("nprofile", NIP19.encode(pp))
) )
def renderAddressPointer( def renderAddressPointer(
store: Store,
addr: snow.AddressPointer addr: snow.AddressPointer
): Resource[IO, HtmlDivElement[IO]] = ): Resource[IO, HtmlDivElement[IO]] =
div( div(
@ -105,15 +104,13 @@ object Components {
entry("author (pubkey hex)", addr.author.value.toHex), entry("author (pubkey hex)", addr.author.value.toHex),
entry("identifier", addr.d), entry("identifier", addr.d),
entry("kind", addr.kind.toString), entry("kind", addr.kind.toString),
if addr.relays.size > 0 then relayHints(store, addr.relays),
Some(entry("relay hints", addr.relays.reduce((a, b) => s"$a, $b")))
else None,
nip19_21("naddr", NIP19.encode(addr)) nip19_21("naddr", NIP19.encode(addr))
) )
def renderEvent( def renderEvent(
event: Event, store: Store,
store: Store event: Event
): Resource[IO, HtmlDivElement[IO]] = ): Resource[IO, HtmlDivElement[IO]] =
div( div(
cls := "text-md", cls := "text-md",
@ -239,5 +236,49 @@ object Components {
) )
) )
private def relayHints(
store: Store,
relays: List[String]
): Resource[IO, HtmlDivElement[IO]] =
SignallingRef[IO].of(false).toResource.flatMap { active =>
val value =
if relays.size > 0 then relays.reduce((a, b) => s"$a, $b") else ""
div(
cls := "flex items-center space-x-3",
span(cls := "font-bold", "relay hints "),
span(Styles.mono, cls := "max-w-xl", value),
active.map {
case true =>
div(
input.withSelf { self =>
(
onKeyPress --> (_.foreach(evt =>
evt.key match {
case "Enter" =>
self.value.get.flatMap(url =>
if url.startsWith("wss://") || url.startsWith("ws://")
then
store.input.update(
_.trim() ++ " + " ++ url
) >> active.set(false)
else IO.unit
)
case _ => IO.unit
}
))
)
}
)
case false =>
button(
Styles.buttonSmall,
"add relay hint",
onClick --> (_.foreach(_ => active.set(true)))
)
}
)
}
private val external = img(cls := "inline w-4 ml-2", src := "ext.svg") private val external = img(cls := "inline w-4 ml-2", src := "ext.svg")
} }

View File

@ -111,15 +111,16 @@ object Main extends IOWebApp {
store.result.map { store.result.map {
case Left(msg) => div(msg) case Left(msg) => div(msg)
case Right(bytes: ByteVector32) => render32Bytes(bytes) case Right(bytes: ByteVector32) => render32Bytes(bytes)
case Right(event: Event) => renderEvent(event, store) case Right(event: Event) => renderEvent(store, event)
case Right(pp: ProfilePointer) => renderProfilePointer(pp) case Right(pp: ProfilePointer) => renderProfilePointer(store, pp)
case Right(evp: EventPointer) => renderEventPointer(evp) case Right(evp: EventPointer) => renderEventPointer(store, evp)
case Right(sk: PrivateKey) => case Right(sk: PrivateKey) =>
renderProfilePointer( renderProfilePointer(
store,
ProfilePointer(pubkey = sk.publicKey.xonly), ProfilePointer(pubkey = sk.publicKey.xonly),
Some(sk) Some(sk)
) )
case Right(addr: AddressPointer) => renderAddressPointer(addr) case Right(addr: AddressPointer) => renderAddressPointer(store, addr)
} }
) )
} }

View File

@ -12,31 +12,48 @@ type Result = Either[
] ]
object Parser { object Parser {
def parseInput(input: String): Result = if input == "" then Left("") val additions = raw" *\+ *".r
else
ByteVector def parseInput(input: String): Result =
.fromHex(input) if input == "" then Left("")
.flatMap(b => Try(Right(ByteVector32(b))).toOption) else {
.getOrElse( val spl = additions.split(input)
NIP19.decode(input) match { val result = ByteVector
case Right(pp: ProfilePointer) => Right(pp) .fromHex(spl.head)
case Right(evp: EventPointer) => Right(evp) .flatMap(b => Try(Right(ByteVector32(b))).toOption)
case Right(sk: PrivateKey) => Right(sk) .getOrElse(
case Right(addr: AddressPointer) => Right(addr) NIP19.decode(spl.head) match {
case Left(_) => case Right(pp: ProfilePointer) => Right(pp)
parse(input) match { case Right(evp: EventPointer) => Right(evp)
case Left(err: io.circe.ParsingFailure) => case Right(sk: PrivateKey) => Right(sk)
Left("not valid JSON or NIP-19 code") case Right(addr: AddressPointer) => Right(addr)
case Right(json) => case Left(_) =>
json parse(input) match {
.as[Event] case Left(err: io.circe.ParsingFailure) =>
.leftMap { err => Left("not valid JSON or NIP-19 code")
err.pathToRootString match { case Right(json) =>
case None => s"decoding ${err.pathToRootString}" json
case Some(path) => s"field $path is missing or wrong" .as[Event]
.leftMap { err =>
err.pathToRootString match {
case None => s"decoding ${err.pathToRootString}"
case Some(path) => s"field $path is missing or wrong"
}
} }
} }
} }
} )
)
val extraRelays = spl
.drop(1)
.toList
.filter(e => e.startsWith("wss://") || e.startsWith("ws://"))
result.map {
case a: AddressPointer => a.copy(relays = a.relays ::: extraRelays)
case p: ProfilePointer => p.copy(relays = p.relays ::: extraRelays)
case e: EventPointer => e.copy(relays = e.relays ::: extraRelays)
case r => r
}
}
} }