start from a blank slate again.

This commit is contained in:
fiatjaf 2023-03-18 18:07:07 -03:00
parent 6fb5b00dd0
commit 9366b3820a
17 changed files with 13 additions and 462 deletions

View File

@ -1 +1 @@
nostr-army-knife single-page app built with scalajs. # nostr army knife

View File

@ -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.'))

View File

@ -1,14 +1,9 @@
enablePlugins(ScalaJSPlugin) enablePlugins(ScalaJSPlugin)
name := "app" name := "nostr-army-knife"
scalaVersion := "2.13.7" scalaVersion := "2.13.7"
scalaJSUseMainModuleInitializer := true scalaJSUseMainModuleInitializer := true
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.1.0" libraryDependencies += "com.armanbilge" %%% "calico" % "0.2.0-RC2"
libraryDependencies += "me.shadaj" %%% "slinky-core" % "0.7.0" libraryDependencies += "com.fiatjaf" %%% "snow" % "0.2.0-RC2"
libraryDependencies += "me.shadaj" %%% "slinky-web" % "0.7.0"
libraryDependencies ++= Seq(
"io.circe" %%% "circe-core",
"io.circe" %%% "circe-parser"
).map(_ % "0.14.1")

View File

@ -1,3 +0,0 @@
window.React = require('react')
window.ReactDOM = require('react-dom')
window.Nostr = require('nostr-tools')

View File

@ -1,5 +1,6 @@
<meta charset=utf-8> <meta charset=utf-8>
<title>nostr army knife - nak</title> <title>nostr army knife</title>
<body></body> <script src="https://cdn.tailwindcss.com"></script>
<script src=globals.bundle.js></script> <body class="bg-amber-600 text-white m-0">
<script src=target/scala-2.13/app-fastopt/main.js></script> <main id="main" />
<script src=/target/esbuild/bundle.js></script>

View File

@ -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"
}
}

View File

@ -1 +1 @@
sbt.version=1.5.5 sbt.version=1.7.1

View File

@ -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")

View File

@ -0,0 +1 @@

View File

@ -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)
)
)
}
}

View File

@ -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)
)
}
}

View File

@ -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: _*)
}
}

View File

@ -1,8 +0,0 @@
package app.handlers
import slinky.core.FunctionalComponent
trait Handler {
def handles(value: String): Boolean
val component: FunctionalComponent[String]
}

View File

@ -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))
)
)
}
}

View File

@ -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")
)("_______")
)
)
)
}
}

View File

@ -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")
)
}
}

View File

@ -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
}