mirror of
https://github.com/fiatjaf/nak.git
synced 2024-12-22 18:15:51 -05:00
start from a blank slate again.
This commit is contained in:
parent
6fb5b00dd0
commit
9366b3820a
|
@ -1 +1 @@
|
|||
nostr-army-knife single-page app built with scalajs.
|
||||
# nostr army knife
|
||||
|
|
27
build.js
27
build.js
|
@ -1,27 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const esbuild = require('esbuild')
|
||||
const alias = require('esbuild-plugin-alias')
|
||||
const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default
|
||||
|
||||
const prod = process.argv.indexOf('prod') !== -1
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: ['globals.js'],
|
||||
outfile: 'globals.bundle.js',
|
||||
bundle: true,
|
||||
plugins: [
|
||||
alias({
|
||||
stream: require.resolve('readable-stream')
|
||||
}),
|
||||
nodeGlobals({buffer: true})
|
||||
],
|
||||
define: {
|
||||
window: 'self',
|
||||
global: 'self'
|
||||
},
|
||||
sourcemap: prod ? false : 'inline',
|
||||
minify: prod
|
||||
})
|
||||
.then(() => console.log('build success.'))
|
11
build.sbt
11
build.sbt
|
@ -1,14 +1,9 @@
|
|||
enablePlugins(ScalaJSPlugin)
|
||||
|
||||
name := "app"
|
||||
name := "nostr-army-knife"
|
||||
scalaVersion := "2.13.7"
|
||||
|
||||
scalaJSUseMainModuleInitializer := true
|
||||
|
||||
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.1.0"
|
||||
libraryDependencies += "me.shadaj" %%% "slinky-core" % "0.7.0"
|
||||
libraryDependencies += "me.shadaj" %%% "slinky-web" % "0.7.0"
|
||||
libraryDependencies ++= Seq(
|
||||
"io.circe" %%% "circe-core",
|
||||
"io.circe" %%% "circe-parser"
|
||||
).map(_ % "0.14.1")
|
||||
libraryDependencies += "com.armanbilge" %%% "calico" % "0.2.0-RC2"
|
||||
libraryDependencies += "com.fiatjaf" %%% "snow" % "0.2.0-RC2"
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
window.React = require('react')
|
||||
window.ReactDOM = require('react-dom')
|
||||
window.Nostr = require('nostr-tools')
|
|
@ -1,5 +1,6 @@
|
|||
<meta charset=utf-8>
|
||||
<title>nostr army knife - nak</title>
|
||||
<body></body>
|
||||
<script src=globals.bundle.js></script>
|
||||
<script src=target/scala-2.13/app-fastopt/main.js></script>
|
||||
<title>nostr army knife</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<body class="bg-amber-600 text-white m-0">
|
||||
<main id="main" />
|
||||
<script src=/target/esbuild/bundle.js></script>
|
||||
|
|
15
package.json
15
package.json
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"scripts": {
|
||||
"start": "./build.js && sbt ~fastLinkJS",
|
||||
"build": "./build.js prod && ./sbtx fullLinkJS && mkdir -p dist/target/scala-2.13/app-fastopt/ && cp index.html globals.bundle.js dist/ && cp target/scala-2.13/app-opt/main.js dist/target/scala-2.13/app-fastopt/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"esbuild": "^0.14.23",
|
||||
"esbuild-plugin-alias": "^0.2.1",
|
||||
"events": "^3.3.0",
|
||||
"nostr-tools": "^0.22.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
sbt.version=1.5.5
|
||||
sbt.version=1.7.1
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0")
|
||||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0")
|
||||
addSbtPlugin("com.fiatjaf" % "sbt-esbuild" % "0.1.1")
|
||||
|
|
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user