mirror of
https://github.com/fiatjaf/nak.git
synced 2024-10-30 09:09:08 -04:00
nip19 parsing.
This commit is contained in:
parent
b966c8a1ac
commit
d54b776c38
86
src/main/scala/Components.scala
Normal file
86
src/main/scala/Components.scala
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import cats.data.*
|
||||||
|
import cats.effect.*
|
||||||
|
import cats.effect.syntax.all.*
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import fs2.concurrent.*
|
||||||
|
import fs2.dom.{Event => _, *}
|
||||||
|
import io.circe.parser.*
|
||||||
|
import io.circe.syntax.*
|
||||||
|
import calico.*
|
||||||
|
import calico.html.io.{*, given}
|
||||||
|
import calico.syntax.*
|
||||||
|
import scoin.*
|
||||||
|
import snow.*
|
||||||
|
|
||||||
|
object Components {
|
||||||
|
def renderEventPointer(
|
||||||
|
evp: snow.EventPointer
|
||||||
|
): Resource[IO, HtmlDivElement[IO]] =
|
||||||
|
div(
|
||||||
|
cls := "text-md",
|
||||||
|
entry("event id (hex)", evp.id),
|
||||||
|
if evp.relays.size > 0 then
|
||||||
|
Some(entry("relay hints", evp.relays.reduce((a, b) => s"$a, $b")))
|
||||||
|
else None,
|
||||||
|
evp.author.map { pk =>
|
||||||
|
entry("author hint (pubkey hex)", pk.value.toHex)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def renderProfilePointer(
|
||||||
|
pp: snow.ProfilePointer,
|
||||||
|
sk: Option[PrivateKey] = None
|
||||||
|
): Resource[IO, HtmlDivElement[IO]] =
|
||||||
|
div(
|
||||||
|
cls := "text-md",
|
||||||
|
sk.map { k => entry("private key (hex)", k.value.toHex) },
|
||||||
|
entry("public key (hex)", pp.pubkey.value.toHex),
|
||||||
|
if pp.relays.size > 0 then
|
||||||
|
Some(entry("relay hints", pp.relays.reduce((a, b) => s"$a, $b")))
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def renderAddressPointer(
|
||||||
|
addr: snow.AddressPointer
|
||||||
|
): Resource[IO, HtmlDivElement[IO]] =
|
||||||
|
div(
|
||||||
|
cls := "text-md",
|
||||||
|
entry("author (pubkey hex)", addr.author.value.toHex),
|
||||||
|
entry("identifier", addr.d),
|
||||||
|
entry("kind", addr.kind.toString),
|
||||||
|
if addr.relays.size > 0 then
|
||||||
|
Some(entry("relay hints", addr.relays.reduce((a, b) => s"$a, $b")))
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def renderEvent(event: Event): Resource[IO, HtmlDivElement[IO]] =
|
||||||
|
div(
|
||||||
|
cls := "text-md",
|
||||||
|
List(("pubkey", event.pubkey), ("id", event.id), ("sig", event.sig))
|
||||||
|
.filter((_, v) => v.isEmpty)
|
||||||
|
.map { (label, _) => entry("property missing", label) },
|
||||||
|
entry("serialized event", event.serialized),
|
||||||
|
entry("implied event id", event.hash.toHex),
|
||||||
|
entry(
|
||||||
|
"does the implied event id match the given event id?",
|
||||||
|
event.id == Some(event.hash.toHex) match {
|
||||||
|
case true => "yes"; case false => "no"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
entry(
|
||||||
|
"is signature valid?",
|
||||||
|
event.isValid match {
|
||||||
|
case true => "yes"; case false => "no"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private def entry(
|
||||||
|
key: String,
|
||||||
|
value: String
|
||||||
|
): Resource[IO, HtmlDivElement[IO]] =
|
||||||
|
div(
|
||||||
|
span(cls := "font-bold", key + " "),
|
||||||
|
span(Styles.mono, value)
|
||||||
|
)
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import scoin.*
|
||||||
import snow.*
|
import snow.*
|
||||||
|
|
||||||
import Utils.*
|
import Utils.*
|
||||||
|
import Components.*
|
||||||
|
|
||||||
object Store {
|
object Store {
|
||||||
def apply(window: Window[IO]): Resource[IO, Store] = {
|
def apply(window: Window[IO]): Resource[IO, Store] = {
|
||||||
|
@ -105,6 +106,7 @@ object Main extends IOWebApp {
|
||||||
(
|
(
|
||||||
cls := "w-full max-h-96 p-3 rounded",
|
cls := "w-full max-h-96 p-3 rounded",
|
||||||
styleAttr := "min-height: 280px; font-family: monospace",
|
styleAttr := "min-height: 280px; font-family: monospace",
|
||||||
|
spellCheck := false,
|
||||||
placeholder := "paste something nostric",
|
placeholder := "paste something nostric",
|
||||||
onInput --> (_.foreach(_ =>
|
onInput --> (_.foreach(_ =>
|
||||||
self.value.get.flatMap(store.input.set)
|
self.value.get.flatMap(store.input.set)
|
||||||
|
@ -121,47 +123,18 @@ object Main extends IOWebApp {
|
||||||
store.input.map { input =>
|
store.input.map { input =>
|
||||||
if input.trim() == "" then div("")
|
if input.trim() == "" then div("")
|
||||||
else
|
else
|
||||||
decode[Event](input) match {
|
Parser.parseInput(input) match {
|
||||||
case Left(err: io.circe.ParsingFailure) =>
|
case Left(msg) => div(msg)
|
||||||
div("not valid JSON")
|
case Right(event: Event) =>
|
||||||
case Left(err: io.circe.DecodingFailure) =>
|
renderEvent(event)
|
||||||
err.pathToRootString match {
|
case Right(pp: ProfilePointer) => renderProfilePointer(pp)
|
||||||
case None => div(s"decoding ${err.pathToRootString}")
|
case Right(evp: EventPointer) => renderEventPointer(evp)
|
||||||
case Some(path) => div(s"field $path is missing or wrong")
|
case Right(sk: PrivateKey) =>
|
||||||
}
|
renderProfilePointer(
|
||||||
case Right(event) =>
|
ProfilePointer(pubkey = sk.publicKey.xonly),
|
||||||
div(
|
Some(sk)
|
||||||
cls := "text-md",
|
|
||||||
div(
|
|
||||||
span(cls := "font-bold", "serialized event "),
|
|
||||||
span(Styles.mono, event.serialized)
|
|
||||||
),
|
|
||||||
div(
|
|
||||||
span(cls := "font-bold", "implied event id "),
|
|
||||||
span(Styles.mono, event.hash.toHex)
|
|
||||||
),
|
|
||||||
div(
|
|
||||||
span(
|
|
||||||
cls := "font-bold",
|
|
||||||
"does the implied event id match the given event id? "
|
|
||||||
),
|
|
||||||
span(
|
|
||||||
Styles.mono,
|
|
||||||
event.id == event.hash.toHex match {
|
|
||||||
case true => "yes"; case false => "no"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
div(
|
|
||||||
span(cls := "font-bold", "is signature valid? "),
|
|
||||||
span(
|
|
||||||
Styles.mono,
|
|
||||||
event.isValid match {
|
|
||||||
case true => "yes"; case false => "no"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
case Right(addr: AddressPointer) => renderAddressPointer(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
33
src/main/scala/Parser.scala
Normal file
33
src/main/scala/Parser.scala
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import io.circe.parser.*
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import scoin.*
|
||||||
|
import snow.*
|
||||||
|
|
||||||
|
import Components.*
|
||||||
|
|
||||||
|
object Parser {
|
||||||
|
def parseInput(input: String): Either[
|
||||||
|
String,
|
||||||
|
Event | PrivateKey | AddressPointer | EventPointer | ProfilePointer
|
||||||
|
] =
|
||||||
|
NIP19.decode(input) match {
|
||||||
|
case Right(pp: ProfilePointer) => Right(pp)
|
||||||
|
case Right(evp: EventPointer) => Right(evp)
|
||||||
|
case Right(sk: PrivateKey) => Right(sk)
|
||||||
|
case Right(addr: AddressPointer) => Right(addr)
|
||||||
|
case Left(_) =>
|
||||||
|
parse(input) match {
|
||||||
|
case Left(err: io.circe.ParsingFailure) =>
|
||||||
|
Left("not valid JSON or NIP-19 code")
|
||||||
|
case Right(json) =>
|
||||||
|
json
|
||||||
|
.as[Event]
|
||||||
|
.leftMap { err =>
|
||||||
|
err.pathToRootString match {
|
||||||
|
case None => s"decoding ${err.pathToRootString}"
|
||||||
|
case Some(path) => s"field $path is missing or wrong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user