diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 17e22584..19b5665f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,7 +10,7 @@ on:
# tags: ["v*"]
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
Run-Tests:
@@ -23,12 +23,12 @@ jobs:
fetch-depth: 0 # Needed for the release tag // `git fetch --tags` will also work
- name: Setup Java and Scala
uses: olafurpg/setup-scala@v14
- # - name: Setup Node
- # uses: actions/setup-node@v2
- # with:
- # node-version: "16" # or whatever
- # - name: Setup Scala.JS
- # uses: japgolly/setup-scalajs@v1
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: "lts/*"
+ - name: Setup Scala.JS
+ uses: japgolly/setup-scalajs@v1
- name: Cache sbt
uses: coursier/cache-action@v6.3
- name: Cache embedmongo
@@ -37,8 +37,8 @@ jobs:
path: ~/.embedmongo
key: ${{ runner.os }}-embedmongo-4.7.0
restore-keys: |
- ${{ runner.os }}-embedmongo-4.7.0
+ ${{ runner.os }}-embedmongo-4.7.0
### Compile and TESTS ###
- run: sbt -mem 2048 -J-Xmx5120m "test"
- # env:
- # NODE_OPTIONS: "--openssl-legacy-provider"
+ env:
+ NODE_OPTIONS: "--openssl-legacy-provider"
diff --git a/README.md b/README.md
index 1f44cbf3..37fd793e 100644
--- a/README.md
+++ b/README.md
@@ -25,3 +25,18 @@ A cloud-based agent that forwards messages to mobile devices.
- [WIP] `MediatorCoordination 2.0` - https://didcomm.org/mediator-coordination/2.0
- [DONE] `Pickup 3` - https://didcomm.org/pickup/3.0
- [DONE] `TrustPing 2.0` - https://didcomm.org/trust-ping/2.0/
+
+## How to run
+
+### server
+
+**Start the server**:
+ - shell> `docker-compose up mongo`
+ - sbt> `mediator/reStart`
+### webapp
+
+The webapp/webpage is atm just to show the QRcode with out of band invitation for the Mediator.
+
+**Compile** - sbt> `webapp / Compile / fastOptJS / webpack`
+
+**Open the webpage for develop** - open> `file:///.../webapp/index-fastopt.html`
diff --git a/build.sbt b/build.sbt
index fc9e6997..797b9530 100644
--- a/build.sbt
+++ b/build.sbt
@@ -3,7 +3,7 @@ resolvers ++= Resolver.sonatypeOssRepos("snapshots")
inThisBuild(
Seq(
- scalaVersion := "3.3.0", // Also update docs/publishWebsite.sh and any ref to scala-3.2.2
+ scalaVersion := "3.3.0", // Also update docs/publishWebsite.sh and any ref to scala-3.3.0
)
)
@@ -20,8 +20,8 @@ lazy val V = new {
// // val scalajsLogging = "1.1.2-SNAPSHOT" //"1.1.2"
// // https://mvnrepository.com/artifact/dev.zio/zio
-// val zio = "2.0.13"
-// val zioJson = "0.4.2"
+ val zio = "2.0.13"
+ val zioJson = "0.4.2"
// val zioMunitTest = "0.1.1"
val zioHttp = "0.0.5"
val zioConfig = "4.0.0-RC16"
@@ -37,6 +37,12 @@ lazy val V = new {
val zioTestSbt = "2.0.15"
val zioTestMagnolia = "2.0.15"
+ // For WEBAPP
+ val laminar = "15.0.1"
+ val waypoint = "6.0.0"
+ val upickle = "3.1.0"
+ // https://www.npmjs.com/package/material-components-web
+ val materialComponents = "12.0.0"
}
/** Dependencies */
@@ -56,9 +62,9 @@ lazy val D = new {
// val dom = Def.setting("org.scala-js" %%% "scalajs-dom" % V.scalajsDom)
-// val zio = Def.setting("dev.zio" %%% "zio" % V.zio)
+ val zio = Def.setting("dev.zio" %%% "zio" % V.zio)
// val zioStreams = Def.setting("dev.zio" %%% "zio-streams" % V.zio)
-// val zioJson = Def.setting("dev.zio" %%% "zio-json" % V.zioJson)
+ val zioJson = Def.setting("dev.zio" %%% "zio-json" % V.zioJson)
val zioHttp = Def.setting("dev.zio" %% "zio-http" % V.zioHttp)
val zioConfig = Def.setting("dev.zio" %% "zio-config" % V.zioConfig)
@@ -80,6 +86,18 @@ lazy val D = new {
val zioTest = Def.setting("dev.zio" %% "zio-test" % V.zioTest % Test)
val zioTestSbt = Def.setting("dev.zio" %% "zio-test-sbt" % V.zioTestSbt % Test)
val zioTestMagnolia = Def.setting("dev.zio" %% "zio-test-magnolia" % V.zioTestMagnolia % Test)
+
+ // For WEBAPP
+ val laminar = Def.setting("com.raquo" %%% "laminar" % V.laminar)
+ val waypoint = Def.setting("com.raquo" %%% "waypoint" % V.waypoint)
+ val upickle = Def.setting("com.lihaoyi" %%% "upickle" % V.upickle)
+}
+
+/** NPM Dependencies */
+lazy val NPM = new {
+ val qrcode = Seq("qrcode-generator" -> "1.4.4")
+
+ val materialDesign = Seq("material-components-web" -> V.materialComponents)
}
inThisBuild(
@@ -123,13 +141,13 @@ lazy val scalaJSBundlerConfigure: Project => Project =
scalaJSLinkerConfig ~= {
_.withSourceMap(false) // disabled because it somehow triggers warnings and errors
.withModuleKind(ModuleKind.CommonJSModule) // ModuleKind.ESModule
- // must be set to ModuleKind.CommonJSModule in projects where ScalaJSBundler plugin is enabled
- .withJSHeader(
- """/* FMGP scala-did examples and tool
- | * https://github.com/FabioPinheiro/scala-did
- | * Copyright: Fabio Pinheiro - fabiomgpinheiro@gmail.com
- | */""".stripMargin.trim() + "\n"
- )
+ // must be set to ModuleKind.CommonJSModule in projects where ScalaJSBundler plugin is enabled
+ // .withJSHeader(
+ // """/* FMGP scala-did examples and tool
+ // | * https://github.com/FabioPinheiro/scala-did
+ // | * Copyright: Fabio Pinheiro - fabiomgpinheiro@gmail.com
+ // | */""".stripMargin.trim() + "\n"
+ // )
}
)
// .settings( //TODO https://scalacenter.github.io/scalajs-bundler/reference.html#jsdom
@@ -206,9 +224,48 @@ lazy val mediator = project
dockerBaseImage := "openjdk:11",
)
.settings(Test / parallelExecution := false)
+ .settings(
+ // WebScalaJSBundlerPlugin
+ scalaJSProjects := Seq(webapp),
+ /** scalaJSPipeline task runs scalaJSDev when isDevMode is true, runs scalaJSProd otherwise. scalaJSProd task runs
+ * all tasks for production, including Scala.js fullOptJS task and source maps scalaJSDev task runs all tasks for
+ * development, including Scala.js fastOptJS task and source maps.
+ */
+ Assets / pipelineStages := Seq(scalaJSPipeline),
+ // pipelineStages ++= Seq(digest, gzip), //Compression - If you serve your Scala.js application from a web server, you should additionally gzip the resulting .js files.
+ Compile / unmanagedResourceDirectories += baseDirectory.value / "src" / "main" / "extra-resources",
+ // Compile / unmanagedResourceDirectories += (baseDirectory.value.toPath.getParent.getParent / "docs-build" / "target" / "mdoc").toFile,
+ // Compile / unmanagedResourceDirectories += (baseDirectory.value.toPath.getParent.getParent / "serviceworker" / "target" / "scala-3.3.0" / "fmgp-serviceworker-fastopt").toFile,
+ Compile / compile := ((Compile / compile) dependsOn scalaJSPipeline).value,
+ // Frontend dependency configuration
+ Assets / WebKeys.packagePrefix := "public/",
+ Runtime / managedClasspath += (Assets / packageBin).value,
+ )
+ .enablePlugins(WebScalaJSBundlerPlugin)
.dependsOn(httpUtils.jvm) // did, didExample,
.enablePlugins(JavaAppPackaging, DockerPlugin)
+lazy val webapp = project
+ .in(file("webapp"))
+ .settings(publish / skip := true)
+ .settings(Test / test := {})
+ .settings(name := "webapp")
+ .configure(scalaJSBundlerConfigure)
+ .configure(buildInfoConfigure)
+ .settings(
+ libraryDependencies ++= Seq(D.laminar.value, D.waypoint.value, D.upickle.value),
+ libraryDependencies ++= Seq(D.zio.value, D.zioJson.value),
+ libraryDependencies ++= Seq(D.scalaDID.value, D.scalaDID_peer.value),
+ Compile / npmDependencies ++= NPM.qrcode ++ NPM.materialDesign
+ )
+ .settings(
+ stShortModuleNames := true,
+ webpackBundlingMode := BundlingMode.LibraryAndApplication(), // BundlingMode.Application,
+ Compile / scalaJSModuleInitializers += {
+ org.scalajs.linker.interface.ModuleInitializer.mainMethod("fmgp.webapp.App", "main")
+ },
+ )
+
// ############################
// #### Release process #####
// ############################
diff --git a/mediator/src/main/resources/public/atala-prism-logo-suite.svg b/mediator/src/main/resources/public/atala-prism-logo-suite.svg
new file mode 100644
index 00000000..52e4aece
--- /dev/null
+++ b/mediator/src/main/resources/public/atala-prism-logo-suite.svg
@@ -0,0 +1,26 @@
+
diff --git a/mediator/src/main/resources/public/index.html b/mediator/src/main/resources/public/index.html
new file mode 100644
index 00000000..108dfffe
--- /dev/null
+++ b/mediator/src/main/resources/public/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+ IOHK Mediator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala
index bddf1e93..9204d3ee 100644
--- a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala
+++ b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorAgent.scala
@@ -22,6 +22,7 @@ import zio.json.*
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try
+import scala.io.Source
case class MediatorAgent(
override val id: DID,
override val keyStore: KeyStore, // Should we make it lazy with ZIO
@@ -114,39 +115,39 @@ case class MediatorAgent(
MediatorError,
Option[EncryptedMessage]
] =
- ZIO
- .logAnnotate("msgHash", msg.hashCode.toString) {
- for {
- _ <- ZIO.log("receivedMessage")
- maybeSyncReplyMsg <-
- if (!msg.recipientsSubject.contains(id))
- ZIO.logError(s"This mediator '${id.string}' is not a recipient")
- *> ZIO.none
- else
- for {
- messageItemRepo <- ZIO.service[MessageItemRepo]
- _ <- messageItemRepo.insert(MessageItem(msg)) // store all message
- plaintextMessage <- decrypt(msg)
- _ <- didSocketManager.get.flatMap { m => // TODO HACK REMOVE !!!!!!!!!!!!!!!!!!!!!!!!
- ZIO.foreach(m.tapSockets)(_.socketOutHub.publish(TapMessage(msg, plaintextMessage).toJson))
- }
- _ <- mSocketID match
+ ZIO
+ .logAnnotate("msgHash", msg.hashCode.toString) {
+ for {
+ _ <- ZIO.log("receivedMessage")
+ maybeSyncReplyMsg <-
+ if (!msg.recipientsSubject.contains(id))
+ ZIO.logError(s"This mediator '${id.string}' is not a recipient")
+ *> ZIO.none
+ else
+ for {
+ messageItemRepo <- ZIO.service[MessageItemRepo]
+ _ <- messageItemRepo.insert(MessageItem(msg)) // store all message
+ plaintextMessage <- decrypt(msg)
+ _ <- didSocketManager.get.flatMap { m => // TODO HACK REMOVE !!!!!!!!!!!!!!!!!!!!!!!!
+ ZIO.foreach(m.tapSockets)(_.socketOutHub.publish(TapMessage(msg, plaintextMessage).toJson))
+ }
+ _ <- mSocketID match
+ case None => ZIO.unit
+ case Some(socketID) =>
+ plaintextMessage.from match
case None => ZIO.unit
- case Some(socketID) =>
- plaintextMessage.from match
- case None => ZIO.unit
- case Some(from) =>
- didSocketManager.update {
- _.link(from.asFROMTO, socketID)
- }
- // TODO Store context of the decrypt unwarping
- // TODO SreceiveMessagetore context with MsgID and PIURI
- protocolHandler <- ZIO.service[ProtocolExecuter[Services]]
- ret <- protocolHandler
- .execute(plaintextMessage)
- .tapError(ex => ZIO.logError(s"Error when execute Protocol: $ex"))
- } yield ret
- } yield maybeSyncReplyMsg
+ case Some(from) =>
+ didSocketManager.update {
+ _.link(from.asFROMTO, socketID)
+ }
+ // TODO Store context of the decrypt unwarping
+ // TODO SreceiveMessagetore context with MsgID and PIURI
+ protocolHandler <- ZIO.service[ProtocolExecuter[Services]]
+ ret <- protocolHandler
+ .execute(plaintextMessage)
+ .tapError(ex => ZIO.logError(s"Error when execute Protocol: $ex"))
+ } yield ret
+ } yield maybeSyncReplyMsg
}
.provideSomeLayer( /*resolverLayer ++ indentityLayer ++*/ protocolHandlerLayer)
@@ -259,12 +260,28 @@ object MediatorAgent {
.text(s"The content-type must be ${MediaTypes.SIGNED.typ} or ${MediaTypes.ENCRYPTED.typ}")
.setStatus(Status.BadRequest)
)
+ case req @ Method.GET -> !! => { // html.Html.fromDomElement()
+ val data = Source.fromResource(s"public/index.html").mkString("")
+ ZIO.log("index.html") *> ZIO.succeed(Response.html(data))
+ }
}: Http[
Operations & Resolver & MessageDispatcher & MediatorAgent & MessageItemRepo & UserAccountRepo,
Throwable,
Request,
Response
]
+ } ++ Http.fromResource(s"public/webapp-fastopt-library.js").when {
+ case Method.GET -> !! / "public" / "webapp-fastopt-library.js" => true
+ case _ => false
+ } ++ {
+ Http.fromResource(s"public/webapp-fastopt-bundle.js").when {
+ case Method.GET -> !! / "public" / path => true
+ // Response(
+ // body = Body.fromStream(ZStream.fromIterator(Source.fromResource(s"public/$path").iter).map(_.toByte)),
+ // headers = Headers(HeaderNames.contentType, HeaderValues.applicationJson),
+ // )
+ case _ => false
+ }
} @@
HttpAppMiddleware.cors(
zio.http.middleware.Cors.CorsConfig(
@@ -272,7 +289,7 @@ object MediatorAgent {
allowedMethods = Some(Set(Method.GET, Method.POST, Method.OPTIONS)),
)
)
- @@
+ @@
HttpAppMiddleware.updateHeaders(headers =>
Headers(
headers.map(h =>
@@ -281,5 +298,5 @@ object MediatorAgent {
} else h
)
)
- )
+ )
}
diff --git a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorStandalone.scala b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorStandalone.scala
index 7a0be55c..85dd5198 100644
--- a/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorStandalone.scala
+++ b/mediator/src/main/scala/io/iohk/atala/mediator/app/MediatorStandalone.scala
@@ -126,6 +126,7 @@ object MediatorStandalone extends ZIOAppDefault {
.fork
_ <- ZIO.log(s"Mediator Started")
_ <- myServer.join *> ZIO.log(s"Mediator End")
+ _ <- ZIO.log(s"*" * 100)
} yield ()
}
diff --git a/webapp/README.md b/webapp/README.md
new file mode 100644
index 00000000..d13af100
--- /dev/null
+++ b/webapp/README.md
@@ -0,0 +1,28 @@
+# WEBAPP module
+
+## Compile
+
+Run sbt with the following NODE_OPTIONS.
+
+This will prevent `Error: error:0308010C:digital envelope routines::unsupported`. See [Troubleshooting (Node v17)](../README.md#Troubleshooting)
+
+```shell
+NODE_OPTIONS=--openssl-legacy-provider sbt
+```
+
+## build and run app (open chrome)
+
+`sbt>` `webapp / Compile / fastOptJS / webpack`
+
+open `file:///home/fabio/workspace/ScalaDID/webapp/index-fastopt.html#/`
+
+google-chrome-stable --disable-web-security --user-data-dir="/tmp/chrome_tmp" --new-window file:///home/fabio/workspace/ScalaDID/webapp/index-fastopt.html#/
+
+When developing in a recompile-test iteration you can sbt to monitor source files and re-run.
+```sbt
+~ webapp / Compile / fastOptJS / webpack
+```
+
+## TODO LIST:
+
+- Update to the [material-web version 3](https://github.com/material-components/material-web#readme).
diff --git a/webapp/index-fastopt.html b/webapp/index-fastopt.html
new file mode 100644
index 00000000..de05ff99
--- /dev/null
+++ b/webapp/index-fastopt.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+ [FAST] fmgp ipfs webapp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/index-fullopt.html b/webapp/index-fullopt.html
new file mode 100644
index 00000000..1929c213
--- /dev/null
+++ b/webapp/index-fullopt.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ [FULL] fmgp ipfs webapp
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/src/main/scala/fmgp/webapp/App.scala b/webapp/src/main/scala/fmgp/webapp/App.scala
new file mode 100644
index 00000000..afbd0456
--- /dev/null
+++ b/webapp/src/main/scala/fmgp/webapp/App.scala
@@ -0,0 +1,49 @@
+package fmgp.webapp
+
+import scala.scalajs.js.annotation._
+
+import scalajs.js
+import org.scalajs.dom
+import com.raquo.laminar.api.L._
+import com.raquo.waypoint._
+
+import MyRouter._
+import com.raquo.airstream.ownership.ManualOwner
+
+import org.scalajs.dom.ServiceWorkerRegistration
+import scala.scalajs.js.JSON
+object App {
+
+ def main( /*args: Array[String]*/ ): Unit = {
+
+ // This div, its id and contents are defined in index-fastopt.html and index-fullopt.html files
+ lazy val container = dom.document.getElementById("app-container")
+
+ lazy val appElement = {
+ div(
+ AppUtils.drawer(linkPages, MyRouter.router.currentPageSignal),
+ AppUtils.drawerScrim,
+ AppUtils.topBarHeader(MyRouter.router.currentPageSignal.map { case p: MediatorPage.type =>
+ "IOHK DID Comm Mediator"
+ // case p => p.title
+ }),
+ mainTag(
+ className("mdc-top-app-bar--fixed-adjust"),
+ child <-- $selectedApp.signal
+ )
+ )
+ }
+
+ // Wait until the DOM is loaded, otherwise app-container element might not exist
+ renderOnDomContentLoaded(container, appElement)
+
+ }
+
+ private val $selectedApp = SplitRender(MyRouter.router.currentPageSignal)
+ .collectStatic(MediatorPage)(MediatorInfo())
+
+ private val linkPages: List[Page] = List(
+ MediatorPage,
+ )
+
+}
diff --git a/webapp/src/main/scala/fmgp/webapp/AppUtils.scala b/webapp/src/main/scala/fmgp/webapp/AppUtils.scala
new file mode 100644
index 00000000..41bc4d21
--- /dev/null
+++ b/webapp/src/main/scala/fmgp/webapp/AppUtils.scala
@@ -0,0 +1,167 @@
+package fmgp.webapp
+
+import scala.scalajs.js.annotation.JSExportTopLevel
+import scala.scalajs.js.annotation.JSExport
+
+import org.scalajs.dom
+import com.raquo.laminar.codecs._
+import com.raquo.laminar.api.L._
+
+import MyRouter._
+
+@JSExportTopLevel("AppUtils")
+object AppUtils {
+
+ def onEnterPress = onKeyPress.filter(_.keyCode == dom.ext.KeyCode.Enter)
+
+ val menuClickObserver = Observer[dom.MouseEvent](onNext = ev => {
+ import typings.materialDrawer.mod.MDCDrawer
+ val tmp = MDCDrawer.attachTo(dom.window.document.querySelector(".mdc-drawer"))
+ tmp.open_=(!tmp.open)
+ })
+
+ val optionsClickObserver = Observer[dom.MouseEvent](onNext = ev => {
+ import typings.materialMenu.mod.MDCMenu
+ val tmp = MDCMenu.attachTo(dom.window.document.querySelector(".mdc-menu"))
+ tmp.open_=(!tmp.open)
+ })
+
+ def topBarHeader(title: Signal[String]) = { // (title: String) = {
+ val menuButton = button(
+ className("material-icons mdc-top-app-bar__navigation-icon mdc-icon-button"),
+ aria.label("Options"),
+ onClick --> menuClickObserver,
+ "menu"
+ )
+ typings.materialRipple.mod.MDCRipple.attachTo(menuButton.ref)
+
+ // def makeLi(didName: String, icon: String) =
+ // li(
+ // className("mdc-list-item"),
+ // role("menuitem"),
+ // span(className("mdc-list-item__ripple")),
+ // i(className("material-icons mdc-list-item__graphic"), icon), // FIXME icon make a make a clone of icon
+ // // span(
+ // // className("mdc-list-item__graphic mdc-menu__selection-group-icon"),
+ // // i(aria.label("Atomium"), atomiumSVG)
+ // // ),
+ // span(className("mdc-list-item__text"), didName),
+ // onClick --> Observer[org.scalajs.dom.MouseEvent](onNext =
+ // ev => Global.agentVar.update(e => fmgp.did.AgentProvider.allAgents.get(didName)),
+ // )
+ // )
+
+ val options = {
+ div(
+ className("mdc-menu mdc-menu-surface"),
+ minWidth("200px"),
+ ul(
+ className("mdc-list"),
+ role("menu"),
+ aria.hidden(true),
+ aria.orientation("vertical"),
+ tabIndex(-1),
+ li(className("mdc-list-divider"), role("separator")),
+ li(
+ ul(
+ className("mdc-menu__selection-group"),
+ // Global.dids.map { did => makeLi(did, "person_outline") }
+ )
+ ),
+ )
+ )
+ }
+
+ val optionsButton = button(
+ className("material-icons mdc-top-app-bar__navigation-icon mdc-icon-button"),
+ aria.label("Open navigation menu"),
+ onClick --> optionsClickObserver,
+ "more_vert"
+ )
+ typings.materialMenu.mod.MDCMenu.attachTo(options.ref)
+
+ headerTag(
+ className("mdc-top-app-bar"),
+ div(
+ className("mdc-top-app-bar__row"),
+ sectionTag(
+ className("mdc-top-app-bar__section mdc-top-app-bar__section--align-start"),
+ menuButton,
+ span(className("mdc-top-app-bar__title"), child.text <-- title)
+ ),
+ sectionTag(
+ className("mdc-top-app-bar__section mdc-top-app-bar__section--align-end"),
+ role("toolbar"),
+ a(
+ className("material-icons mdc-top-app-bar__action-item mdc-icon-button"),
+ href("https://github.com/FabioPinheiro/fmgp-generative-design"),
+ i(aria.label("Github")),
+ ),
+ // button(
+ // className("material-icons mdc-top-app-bar__action-item mdc-icon-button"),
+ // aria.label("Search"),
+ // "search"
+ // ),
+ // select(
+ // value <-- Global.agentVar.signal.map(Global.getAgentName(_)),
+ // onChange.mapToValue.map(e => fmgp.did.AgentProvider.allAgents.get(e)) --> Global.agentVar,
+ // Global.dids.map { step => option(value := step, step) }
+ // ),
+ div(
+ className("mdc-menu-surface--anchor"),
+ optionsButton,
+ options
+ )
+ ),
+ ),
+ )
+ }
+
+ val drawerScrim = div(className("mdc-drawer-scrim"))
+ def drawer(linkPages: List[Page], currentPage: Signal[Page]) =
+ asideTag(
+ className("mdc-drawer mdc-drawer--modal"),
+ div(
+ className("mdc-drawer__header"),
+ h3(className("mdc-drawer__title"), "IOHP - Atala PRISM Mediator"),
+ h6(className("mdc-drawer__subtitle"), "atlaprism@iohk.io"),
+ ),
+ div(
+ className("mdc-drawer__content"),
+ navTag(
+ className("mdc-list"),
+ linkPages.map(page =>
+ a(
+ className <-- currentPage.map { p =>
+ if (p == page) "mdc-list-item mdc-list-item--activated" else "mdc-list-item"
+ },
+ aria.current := "page",
+ tabIndex(0),
+ span(className("mdc-list-item__ripple")),
+ i(
+ className("material-icons mdc-list-item__graphic"),
+ aria.hidden(true),
+ page.icon
+ ),
+ navigateTo(page),
+ span(className("mdc-list-item__text"), page.title),
+ ),
+ )
+ )
+ )
+ )
+
+ def myButton(text: String) = {
+ div(
+ className("mdc-touch-target-wrapper"),
+ button(
+ className("mdc-button mdc-button--touch mdc-button--raised"),
+ span(className("mdc-button__ripple")),
+ span(className("mdc-button__touch")),
+ span(className("mdc-button__label"), text),
+ )
+ )
+
+ }
+
+}
diff --git a/webapp/src/main/scala/fmgp/webapp/Global.scala b/webapp/src/main/scala/fmgp/webapp/Global.scala
new file mode 100644
index 00000000..1ecc97b2
--- /dev/null
+++ b/webapp/src/main/scala/fmgp/webapp/Global.scala
@@ -0,0 +1,22 @@
+package fmgp.webapp
+
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import org.scalajs.dom
+import com.raquo.laminar.api.L._
+
+import fmgp.did._
+import fmgp.did.method.peer.DIDPeer
+import fmgp.did.comm.TO
+
+import fmgp.did.comm._
+
+object Global {
+
+ def mediatorDID = FROM(
+ "did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9rOHMtaW50LmF0YWxhcHJpc20uaW8vbWVkaWF0b3IiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19"
+ )
+ def clipboardSideEffect(text: => String): Any => Unit =
+ (_: Any) => { dom.window.navigator.clipboard.writeText(text) }
+
+}
diff --git a/webapp/src/main/scala/fmgp/webapp/MediatorInfo.scala b/webapp/src/main/scala/fmgp/webapp/MediatorInfo.scala
new file mode 100644
index 00000000..f8b67aab
--- /dev/null
+++ b/webapp/src/main/scala/fmgp/webapp/MediatorInfo.scala
@@ -0,0 +1,41 @@
+package fmgp.webapp
+
+import org.scalajs.dom
+import com.raquo.laminar.api.L._
+import typings.qrcodeGenerator
+
+import zio.json._
+import fmgp.did._
+import fmgp.did.comm._
+import fmgp.did.comm.protocol.oobinvitation.OOBInvitation
+
+object MediatorInfo {
+
+ val invitation = OOBInvitation(
+ from = Global.mediatorDID,
+ goal_code = Some("request-mediate"),
+ goal = Some("RequestMediate"),
+ accept = Some(Seq("didcomm/v2")),
+ )
+ val qrCodeData = OutOfBandPlaintext.from(invitation.toPlaintextMessage).makeURI("https://did.fmgp.app/#/")
+
+ val divQRCode = div()
+ {
+ val aux = qrcodeGenerator.mod.^.apply(qrcodeGenerator.TypeNumber.`0`, qrcodeGenerator.ErrorCorrectionLevel.L)
+ aux.addData(qrCodeData)
+ aux.make()
+ divQRCode.ref.innerHTML = aux.createSvgTag(8d)
+ }
+
+ def apply(): HtmlElement = // rootElement
+ div(
+ h1("Invite for the DID Comm Mediator:"),
+ h3("Plaintext out of band invitation:"),
+ p(code(qrCodeData)),
+ pre(code(invitation.toPlaintextMessage.toJsonPretty)),
+ divQRCode,
+ h3("Signed out of band invitation:"),
+ code("TODO"),
+ )
+
+}
diff --git a/webapp/src/main/scala/fmgp/webapp/MyRouter.scala b/webapp/src/main/scala/fmgp/webapp/MyRouter.scala
new file mode 100644
index 00000000..29094868
--- /dev/null
+++ b/webapp/src/main/scala/fmgp/webapp/MyRouter.scala
@@ -0,0 +1,104 @@
+package fmgp.webapp
+
+import com.raquo.laminar.api.L.{_, given}
+import com.raquo.waypoint._
+import org.scalajs.dom
+import upickle.default._
+
+object MyRouter {
+ sealed abstract class Page(
+ val title: String,
+ val icon: String // https://fonts.google.com/icons?selected=Material+Icons+Outlined
+ )
+
+ // case object HomePage extends Page("Home", "home")
+ // case class OOBPage(query_oob: String) extends Page("OutOfBand", "app_shortcut")
+ // case object DocPage extends Page("Doc", "menu_book")
+ // case object AgentKeysPage extends Page("AgentKeys", "key")
+ // // case object DIDPage extends Page("DID", "visibility")
+ // case object AgentDBPage extends Page("MessageDB", "folder_open")
+ // case class ResolverPage(did: String) extends Page("Resolver", "dns")
+ // case object EncryptPage extends Page("Encrypt", "enhanced_encryption")
+ // case object DecryptPage extends Page("Decrypt", "email")
+ // case object BasicMessagePage extends Page("BasicMessage", "message")
+ // case object TrustPingPage extends Page("TrustPing", "network_ping`")
+ // case object TapIntoStreamPage extends Page("TapIntoStream", "chat")
+ // case object DAppStorePage extends Page("DAppStore", "share")
+ case object MediatorPage extends Page("Mediator", "diversity_3")
+
+ // given homePageRW: ReadWriter[HomePage.type] = macroRW
+ // given oobPageRW: ReadWriter[OOBPage] = macroRW
+ // given docPageRW: ReadWriter[DocPage.type] = macroRW
+ // given keysPageRW: ReadWriter[AgentKeysPage.type] = macroRW
+ // given agentDBPageRW: ReadWriter[AgentDBPage.type] = macroRW
+ // given resolverPageRW: ReadWriter[ResolverPage] = macroRW
+ // given encryptPageRW: ReadWriter[EncryptPage.type] = macroRW
+ // given decryptPageRW: ReadWriter[DecryptPage.type] = macroRW
+ // given basicMessagePageRW: ReadWriter[BasicMessagePage.type] = macroRW
+ // given trustPingPageRW: ReadWriter[TrustPingPage.type] = macroRW
+ // given tapIntoStreamPageRW: ReadWriter[TapIntoStreamPage.type] = macroRW
+ // given dAppStorePageRW: ReadWriter[DAppStorePage.type] = macroRW
+ given mediatorPageRW: ReadWriter[MediatorPage.type] = macroRW
+
+ given rw: ReadWriter[Page] = macroRW
+
+ private val routes = List(
+ // // http://localhost:8080/?_oob=eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNTk5ZjM2MzgtYjU2My00OTM3LTk0ODctZGZlNTUwOTlkOTAwIiwiZnJvbSI6ImRpZDpleGFtcGxlOnZlcmlmaWVyIiwiYm9keSI6eyJnb2FsX2NvZGUiOiJzdHJlYW1saW5lZC12cCIsImFjY2VwdCI6WyJkaWRjb21tL3YyIl19fQ
+ // Route.onlyQuery[OOBPage, String]( // OOB
+ // encode = page => page.query_oob,
+ // decode = arg => OOBPage(query_oob = arg),
+ // pattern = (root / endOfSegments) ? (param[String]("_oob")),
+ // Router.localFragmentBasePath
+ // ),
+ // Route[ResolverPage, String](
+ // encode = page => page.did,
+ // decode = arg => ResolverPage(did = arg),
+ // pattern = root / "resolver" / segment[String] / endOfSegments,
+ // Router.localFragmentBasePath
+ // ),
+ // Route.static(HomePage, root / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(DocPage, root / "doc" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(AgentKeysPage, root / "agentkeys" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(AgentDBPage, root / "db" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(EncryptPage, root / "encrypt" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(DecryptPage, root / "decrypt" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(BasicMessagePage, root / "basicmessage" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(TrustPingPage, root / "trustping" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(TapIntoStreamPage, root / "stream" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(DAppStorePage, root / "dapp" / endOfSegments, Router.localFragmentBasePath),
+ // Route.static(MediatorPage, root / "mediator" / endOfSegments, Router.localFragmentBasePath),
+ Route.static(MediatorPage, root / endOfSegments, Router.localFragmentBasePath),
+ )
+
+ val router = new Router[Page](
+ routes = routes,
+ getPageTitle = _.title, // displayed in the browser tab next to favicon
+ serializePage = page => write(page)(rw), // serialize page data for storage in History API log
+ deserializePage = pageStr => read(pageStr)(rw), // deserialize the above
+ // routeFallback = { (_: String) => HomePage },
+ routeFallback = { (_: String) => MediatorPage },
+ )(
+ popStateEvents = windowEvents(_.onPopState), // this is how Waypoint avoids an explicit dependency on Laminar
+ owner = unsafeWindowOwner // this router will live as long as the window
+ )
+
+ // Note: for fragment ('#') URLs this isn't actually needed.
+ // See https://github.com/raquo/Waypoint docs for why this modifier is useful in general.
+ def navigateTo(page: Page): Binder[HtmlElement] = Binder { el =>
+
+ val isLinkElement = el.ref.isInstanceOf[dom.html.Anchor]
+
+ if (isLinkElement) {
+ el.amend(href(router.absoluteUrlForPage(page)))
+ }
+
+ // If element is a link and user is holding a modifier while clicking:
+ // - Do nothing, browser will open the URL in new tab / window / etc. depending on the modifier key
+ // Otherwise:
+ // - Perform regular pushState transition
+ (onClick
+ .filter(ev => !(isLinkElement && (ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)))
+ .preventDefault
+ --> (_ => router.pushState(page))).bind(el)
+ }
+}