mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 09:08:50 +00:00
start from a blank slate again.
This commit is contained in:
1
src/main/scala/Main.scala
Normal file
1
src/main/scala/Main.scala
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package app
|
||||
|
||||
import scala.scalajs.js
|
||||
import org.scalajs.dom
|
||||
import slinky.core.FunctionalComponent
|
||||
import slinky.web.html._
|
||||
import slinky.core.facade.Hooks._
|
||||
|
||||
import app.handlers.{Handler, Nothing, KeyHandling, EventSignatures}
|
||||
|
||||
object Base {
|
||||
val handlers: List[Handler] = List(KeyHandling, EventSignatures, Nothing)
|
||||
|
||||
val component = FunctionalComponent[Unit] { props =>
|
||||
val (typedValue, setTypedValue) = useState("")
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
val saved = dom.window.localStorage.getItem("value")
|
||||
setTypedValue(saved match { case _: String => saved; case _ => "" })
|
||||
},
|
||||
Seq()
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
dom.window.localStorage.setItem("value", typedValue)
|
||||
},
|
||||
Seq(typedValue)
|
||||
)
|
||||
|
||||
val Handler = handlers
|
||||
.find(handler => handler.handles(typedValue))
|
||||
.getOrElse(Nothing)
|
||||
|
||||
div(
|
||||
style := js.Dynamic.literal(
|
||||
fontFamily = "monospace"
|
||||
)
|
||||
)(
|
||||
div(
|
||||
h1("nostr army knife"),
|
||||
p("paste something nostric"),
|
||||
textarea(
|
||||
value := typedValue,
|
||||
onChange := { ev => setTypedValue(ev.target.value) },
|
||||
style := js.Dynamic.literal(
|
||||
padding = "7px",
|
||||
width = "100%",
|
||||
minHeight = "200px"
|
||||
)
|
||||
)
|
||||
),
|
||||
hr(style := js.Dynamic.literal(margin = "18px 0")),
|
||||
div(
|
||||
style := js.Dynamic.literal(
|
||||
width = "90%",
|
||||
margin = "auto"
|
||||
)
|
||||
)(
|
||||
Handler.component(typedValue)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package app.components
|
||||
|
||||
import scala.scalajs.js
|
||||
import slinky.core.{FunctionalComponent, CustomAttribute}
|
||||
import slinky.web.html._
|
||||
|
||||
object Item {
|
||||
val wenkValue = CustomAttribute[String]("data-wenk")
|
||||
val wenkPos = CustomAttribute[String]("data-wenk-pos")
|
||||
|
||||
case class props(label: String, hint: String, content: String)
|
||||
|
||||
val component = FunctionalComponent[props] { props =>
|
||||
div(
|
||||
style := js.Dynamic.literal(
|
||||
marginBottom = "9px",
|
||||
whiteSpace = "pre-wrap",
|
||||
wordWrap = "break-word",
|
||||
wordBreak = "break-all"
|
||||
)
|
||||
)(
|
||||
b(wenkValue := props.hint, wenkPos := "right")(
|
||||
span(props.label)
|
||||
),
|
||||
span(" "),
|
||||
span(props.content)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package app.handlers
|
||||
|
||||
import scala.annotation.{nowarn, tailrec}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.scalajs.js
|
||||
import slinky.core.FunctionalComponent
|
||||
import slinky.web.html._
|
||||
import slinky.core.facade.Hooks._
|
||||
import slinky.core.facade.Fragment
|
||||
import io.circe.{Json, HCursor}
|
||||
import io.circe.parser.{parse}
|
||||
|
||||
import app.modules.Nostr
|
||||
import app.handlers.{Handler}
|
||||
import app.components.{Item}
|
||||
|
||||
object EventSignatures extends Handler {
|
||||
val keymatcher = "^[a-f0-9]{64}$".r
|
||||
|
||||
def badProperties(c: HCursor): Seq[String] = Seq(
|
||||
(
|
||||
c.get[Double]("kind").getOrElse[Double](-1) >= 0 match {
|
||||
case true => None;
|
||||
case false => Some("kind")
|
||||
}
|
||||
),
|
||||
(
|
||||
keymatcher.matches(
|
||||
c.get[String]("pubkey").getOrElse("").toLowerCase()
|
||||
) match {
|
||||
case true => None;
|
||||
case false => Some("pubkey")
|
||||
}
|
||||
),
|
||||
(
|
||||
c.get[String]("content").exists((_) => true) match {
|
||||
case true => None;
|
||||
case false => Some("content")
|
||||
}
|
||||
),
|
||||
(
|
||||
c
|
||||
.get[List[List[String]]]("tags")
|
||||
.exists((_) => true) match {
|
||||
case true => None;
|
||||
case false => Some("tags")
|
||||
}
|
||||
)
|
||||
)
|
||||
.filter(res => res.isDefined)
|
||||
.map(res => res.get)
|
||||
|
||||
override def handles(value: String): Boolean = parse(value) match {
|
||||
case Left(_) => false
|
||||
case Right(json) => {
|
||||
badProperties(json.hcursor).length < 4
|
||||
}
|
||||
}
|
||||
|
||||
type MaybeItem = Future[
|
||||
Either[slinky.core.TagMod[Nothing], slinky.core.TagMod[Nothing]]
|
||||
]
|
||||
|
||||
@nowarn("cat=other")
|
||||
def itemWrongProperties(evtj: String): MaybeItem = Future {
|
||||
val c = parse(evtj).toOption.get.hcursor
|
||||
val bad = badProperties(c)
|
||||
|
||||
if (bad.length > 0) {
|
||||
Left(
|
||||
Item.component(
|
||||
Item.props(
|
||||
"event missing or wrong properties",
|
||||
"",
|
||||
bad.mkString(", ")
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Right(div())
|
||||
}
|
||||
}
|
||||
|
||||
@nowarn("cat=other")
|
||||
def itemSerializedEvent(evtj: String): MaybeItem = Future {
|
||||
val event: js.Dynamic = js.JSON.parse(evtj)
|
||||
|
||||
Right(
|
||||
Item.component(
|
||||
Item.props(
|
||||
"serialized event",
|
||||
"according to nip-01 signature algorithm",
|
||||
Nostr.serializeEvent(event)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("cat=other")
|
||||
def itemEventId(evtj: String): MaybeItem = Future {
|
||||
val event: js.Dynamic = js.JSON.parse(evtj)
|
||||
|
||||
Right(
|
||||
Item.component(
|
||||
Item.props(
|
||||
"event id",
|
||||
"sha256 hash of serialized event",
|
||||
Nostr.getEventHash(event)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("cat=other")
|
||||
def itemEventIdMatches(evtj: String): MaybeItem = Future {
|
||||
val c = parse(evtj).toOption.get.hcursor
|
||||
val event: js.Dynamic = js.JSON.parse(evtj)
|
||||
|
||||
def render(result: Boolean) = Item.component(
|
||||
Item.props(
|
||||
"does the implied event id match the given event id?",
|
||||
"if not, that means you're hashing the event uncorrectly",
|
||||
f"${result match {
|
||||
case true => "yes"; case false => "no"
|
||||
}}"
|
||||
)
|
||||
)
|
||||
|
||||
val hash = Nostr.getEventHash(event)
|
||||
|
||||
c.get[String]("id") match {
|
||||
case Right(id) if id == hash => Right(render(true))
|
||||
case _ => Left(render(false))
|
||||
}
|
||||
}
|
||||
|
||||
@nowarn("cat=other")
|
||||
def itemSignatureValid(evtj: String): MaybeItem = {
|
||||
val event: js.Dynamic = js.JSON.parse(evtj)
|
||||
|
||||
def render(result: Boolean) = Item.component(
|
||||
Item.props(
|
||||
"is signature valid?",
|
||||
"",
|
||||
f"${result match {
|
||||
case true => "yes"; case false => "no"
|
||||
}}"
|
||||
)
|
||||
)
|
||||
|
||||
Nostr.verifySignature(event).toFuture map {
|
||||
case true => Right(render(true))
|
||||
case false => Left(render(false))
|
||||
}
|
||||
}
|
||||
|
||||
val protoElements = List[(String) => MaybeItem](
|
||||
itemWrongProperties,
|
||||
itemSerializedEvent,
|
||||
itemEventId,
|
||||
itemEventIdMatches,
|
||||
itemSignatureValid
|
||||
)
|
||||
|
||||
@nowarn("cat=other")
|
||||
override val component = FunctionalComponent[String] { props =>
|
||||
val (elements, setElements) =
|
||||
useState(Seq.empty[slinky.core.TagMod[Nothing]])
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
def runAndUnwrapUntilFirstLeft(
|
||||
remaining: List[String => Future[
|
||||
Either[slinky.core.TagMod[Nothing], slinky.core.TagMod[Nothing]]
|
||||
]],
|
||||
acc: List[slinky.core.TagMod[Nothing]]
|
||||
): Future[List[slinky.core.TagMod[Nothing]]] = remaining match {
|
||||
case fn :: tail => {
|
||||
fn(props) flatMap {
|
||||
{
|
||||
case Left(el) => runAndUnwrapUntilFirstLeft(Nil, el :: acc)
|
||||
case Right(el) => runAndUnwrapUntilFirstLeft(tail, el :: acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
case Nil => Future { acc.reverse }
|
||||
}
|
||||
|
||||
runAndUnwrapUntilFirstLeft(protoElements, List()) onComplete {
|
||||
case Success(x) => setElements(x)
|
||||
case Failure(err) =>
|
||||
println(f"failed to run through elements: ${err}")
|
||||
}
|
||||
|
||||
() => {}
|
||||
},
|
||||
Seq(props)
|
||||
)
|
||||
|
||||
Fragment(elements: _*)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.handlers
|
||||
|
||||
import slinky.core.FunctionalComponent
|
||||
|
||||
trait Handler {
|
||||
def handles(value: String): Boolean
|
||||
val component: FunctionalComponent[String]
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.handlers
|
||||
|
||||
import scala.util.matching.Regex
|
||||
import scala.scalajs.js
|
||||
import slinky.core.{FunctionalComponent}
|
||||
import slinky.web.html._
|
||||
import slinky.core.facade.Hooks._
|
||||
import slinky.core.facade.Fragment
|
||||
|
||||
import app.modules.Nostr
|
||||
import app.handlers.{Handler}
|
||||
import app.components.{Item}
|
||||
|
||||
object KeyHandling extends Handler {
|
||||
val keymatcher = "^[a-f0-9]{64}$".r
|
||||
|
||||
override def handles(value: String): Boolean =
|
||||
keymatcher.matches(value.toLowerCase())
|
||||
|
||||
override val component = FunctionalComponent[String] { props =>
|
||||
Fragment(
|
||||
Item.component(Item.props("private key", "", props)),
|
||||
Item.component(
|
||||
Item.props("public key", "", Nostr.getPublicKey(props))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.handlers
|
||||
|
||||
import scala.scalajs.js
|
||||
import slinky.core.FunctionalComponent
|
||||
import slinky.web.html._
|
||||
import slinky.core.facade.Hooks._
|
||||
import slinky.core.facade.Fragment
|
||||
|
||||
import app.handlers.{Handler}
|
||||
|
||||
object Nothing extends Handler {
|
||||
override def handles(value: String): Boolean = true
|
||||
|
||||
override val component = FunctionalComponent[String] { props =>
|
||||
Fragment(
|
||||
p("you can paste here"),
|
||||
ul(
|
||||
li("an unsigned event to be hashed and signed"),
|
||||
li("a signed event to have its signature checked"),
|
||||
li("a nostr relay URL to be inspected"),
|
||||
li("a nostr event id we'll try to fetch"),
|
||||
li("a nip05 identifier to be checked"),
|
||||
li(
|
||||
span("contribute a new function: "),
|
||||
a(
|
||||
target := "_blank",
|
||||
href := "https://github.com/fiatjaf/nostr-army-knife",
|
||||
style := js.Dynamic.literal(color = "inherit")
|
||||
)("_______")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package app
|
||||
|
||||
import org.scalajs.dom.document
|
||||
import slinky.web.ReactDOM
|
||||
import slinky.web.html._
|
||||
|
||||
import app.Base._
|
||||
|
||||
object Main {
|
||||
def main(args: Array[String]): Unit = {
|
||||
val div = document.createElement("div")
|
||||
div.id = "root"
|
||||
document.body.appendChild(div)
|
||||
|
||||
ReactDOM.render(
|
||||
Base.component(),
|
||||
document.getElementById("root")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package app.modules
|
||||
|
||||
import scala.scalajs.js.annotation._
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
@JSGlobal
|
||||
object Nostr extends js.Object {
|
||||
def getPublicKey(text: String): String = js.native
|
||||
def getEventHash(evt: js.Dynamic): String = js.native
|
||||
def serializeEvent(evt: js.Dynamic): String = js.native
|
||||
def verifySignature(evt: js.Dynamic): js.Promise[Boolean] = js.native
|
||||
def signEvent(evt: js.Dynamic, privateKey: String): String = js.native
|
||||
}
|
||||
Reference in New Issue
Block a user