mirror of
https://github.com/fiatjaf/nak.git
synced 2024-11-22 08:19:06 -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)
|
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")
|
|
||||||
|
|
|
@ -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>
|
<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>
|
||||||
|
|
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