diff --git a/README.md b/README.md index 604035d..facb9ff 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -nostr-army-knife single-page app built with scalajs. +# nostr army knife diff --git a/build.js b/build.js deleted file mode 100755 index e0e5bdb..0000000 --- a/build.js +++ /dev/null @@ -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.')) diff --git a/build.sbt b/build.sbt index 65a08e0..4e494a0 100644 --- a/build.sbt +++ b/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" diff --git a/globals.js b/globals.js deleted file mode 100644 index 8b549f1..0000000 --- a/globals.js +++ /dev/null @@ -1,3 +0,0 @@ -window.React = require('react') -window.ReactDOM = require('react-dom') -window.Nostr = require('nostr-tools') diff --git a/index.html b/index.html index 8613978..54a4ee1 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ -nostr army knife - nak - - - +nostr army knife + + +
+ diff --git a/package.json b/package.json deleted file mode 100644 index 7d71c19..0000000 --- a/package.json +++ /dev/null @@ -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" - } -} diff --git a/project/build.properties b/project/build.properties index 10fd9ee..22af262 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.7.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 4931e02..dabefb2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -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") diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/scala/Main.scala @@ -0,0 +1 @@ + diff --git a/src/main/scala/app/Base.scala b/src/main/scala/app/Base.scala deleted file mode 100644 index 2e9e261..0000000 --- a/src/main/scala/app/Base.scala +++ /dev/null @@ -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) - ) - ) - } -} diff --git a/src/main/scala/app/components/Item.scala b/src/main/scala/app/components/Item.scala deleted file mode 100644 index fb3ee94..0000000 --- a/src/main/scala/app/components/Item.scala +++ /dev/null @@ -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) - ) - } -} diff --git a/src/main/scala/app/handlers/EventSignatures.scala b/src/main/scala/app/handlers/EventSignatures.scala deleted file mode 100644 index ea391dd..0000000 --- a/src/main/scala/app/handlers/EventSignatures.scala +++ /dev/null @@ -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: _*) - } -} diff --git a/src/main/scala/app/handlers/Handler.scala b/src/main/scala/app/handlers/Handler.scala deleted file mode 100644 index 23eb638..0000000 --- a/src/main/scala/app/handlers/Handler.scala +++ /dev/null @@ -1,8 +0,0 @@ -package app.handlers - -import slinky.core.FunctionalComponent - -trait Handler { - def handles(value: String): Boolean - val component: FunctionalComponent[String] -} diff --git a/src/main/scala/app/handlers/KeyHandling.scala b/src/main/scala/app/handlers/KeyHandling.scala deleted file mode 100644 index ffa322c..0000000 --- a/src/main/scala/app/handlers/KeyHandling.scala +++ /dev/null @@ -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)) - ) - ) - } -} diff --git a/src/main/scala/app/handlers/Nothing.scala b/src/main/scala/app/handlers/Nothing.scala deleted file mode 100644 index 7eeb587..0000000 --- a/src/main/scala/app/handlers/Nothing.scala +++ /dev/null @@ -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") - )("_______") - ) - ) - ) - } -} diff --git a/src/main/scala/app/main.scala b/src/main/scala/app/main.scala deleted file mode 100644 index 79da934..0000000 --- a/src/main/scala/app/main.scala +++ /dev/null @@ -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") - ) - } -} diff --git a/src/main/scala/app/modules/Nostr.scala b/src/main/scala/app/modules/Nostr.scala deleted file mode 100644 index b6954b4..0000000 --- a/src/main/scala/app/modules/Nostr.scala +++ /dev/null @@ -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 -}