Cannot detect Metamask login using ClojureScript - clojurescript

I’m a ClojureScript beginner and I am trying to connect my app to Metamask wallet.
In the past I successfully did it on a pure JS project with the following code:
import Web3 from "web3";
import detectEthereumProvider from "#metamask/detect-provider";
window.addEventListener("load", async function () {
const provider = await detectEthereumProvider();
if (provider !== window.ethereum) {
reject("Failed to connect");
}
await provider.request({ method: "eth_requestAccounts" });
if (provider) {
const web3 = new Web3(provider);
}
})
So I’m trying to translate this into ClojureScript. Here is what I’ve started to do:
(ns token-stream.views
(:require
[re-frame.core :as re-frame]
[web3 :as web3]
["#metamask/detect-provider" :as detectEthereumProvider]))
(detectEthereumProvider/detectEthereumProvider)
Any idea how I could translate the JS code?
Please note that I want to interop web3.js library (no use of cljs-web3).
Thanks

I used cljs-web3:
Deps:
[cljs-web3 "0.19.0-0-11"]
Code:
(require '[cljs-web3.core :as web3])
(require '[cljs-web3.eth :as web3.eth])
;; Create web3 instance from injected web3 (Mist / Metamask)
(web3/web3)
;; And then
(def provides-web3?
(boolean (aget js/window "web3")))
(def web3-instance js/web3
#_(web3/create-web3 "http://localhost:8545/"))
(def network-type
(case (web3/version-network web3-instance)
"1" :local-development ; FIXME: same as main-net
"2" :morden-network ; deprecated
"3" :ropsten-network
"4" :rinkeby-network
"42" :kovan-network
:unknown-network))
(println "account:" (first (web3.eth/accounts web3-instance)))

Related

How do I edit Reitit routes in Reagent?

The routes created with the default reagent template look like this:
;; -------------------------
;; Routes
(def router
(reitit/router
[["/" :index]
["/items"
["" :items]
["/:item-id" :item]]
["/about" :about]]))
If I change the path of one ("/about" to "/test" below), why does it no longer work? There must be something else driving the routing, but I can't seem to figure out what.
;; -------------------------
;; Routes
(def router
(reitit/router
[["/" :index]
["/items"
["" :items]
["/:item-id" :item]]
["/test" :about]]))
This is the default reagent template (lein new reagent...) and I haven't changed anything else in the code. Any help would be greatly appreciated.
Edit - Some additional detail
I poked around in the repl a little bit in this function (from the default template):
(defn init! []
(clerk/initialize!)
(accountant/configure-navigation!
{:nav-handler
(fn [path]
(let [match (reitit/match-by-path router path)
current-page (:name (:data match))
route-params (:path-params match)]
(reagent/after-render clerk/after-render!)
(session/put! :route {:current-page (page-for current-page)
:route-params route-params})
(clerk/navigate-page! path)
))
:path-exists?
(fn [path]
(boolean (reitit/match-by-path router path)))})
(accountant/dispatch-current!)
(mount-root))
Everything looks ok to me. In fact, executing the below steps in the repl successfully navigated the browser to the correct page. I still can't enter the URL directly though.
app:problem.core=> (require '[reitit.frontend :as reitit])
nil
app:problem.core=> (reitit/match-by-path router "/test")
{:template "/test",
:data {:name :about},
:result nil,
:path-params {},
:path "/test",
:query-params {},
:parameters {:path {}, :query {}}}
app:problem.core=> (def match (reitit/match-by-path router "/test"))
#'problem.core/match
app:problem.core=> (:name (:data match))
:about
app:problem.core=> (:path-params match)
{}
app:problem.core=> (def current-page (:name (:data match)))
#'problem.core/current-page
app:problem.core=> (page-for current-page)
#'problem.core/about-page
app:problem.core=> (session/put! :route {:current-page (page-for current-page) :route-params {}})
{:route {:current-page #'problem.core/about-page, :route-params {}}}
app:problem.core=>
It looks like you changed the routes on client-side, in src/cljs/<project_name>/core.cljs, but did not change them server side in src/clj/<project_name>/handler.clj (look under the def app near the bottom of the file).
If your new to developing web applications with Clojure, I'd recommend looking at Luminus, rather than using the Reagent template. It's a much more batteries included-approach, with a lot more documentation. The book "Web Development With Clojure" is written by the same author (who is also a contributor to Reagent), and is also recommended reading.

Upload a json object to S3 using Clojure?

I am trying to figure out how I would upload a Json object to S3 using clojure through lambda. I am not doing any json type checking and I am just trying to write a handler such that as soon as receives a json object, it uploads that object to the bucket. Here is my code:
(ns clojurehandler.s3_clojure_handler
(:gen-class
:name "clojurehandler.S3ClojureHandler"
:implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
(:require [clojure.data.json :as json]
[clojure.string :as s]
[clojure.java.io :as io]
[aws.sdk.s3 :as s3]))
(def cred {:access-key "..", :secret-key ".."})
(defn handle-event [event]
(s3/put-object cred
"bucket-name" "name" event
:content-type "application/json"))
(defn -handleRequest [this is os context]
(handle-event (io/reader is)))
I am using this library: https://github.com/weavejester/clj-aws-s3
which I know has been deprecated but it seems like it should do what I am aiming to do.
I have already set the role of my lambda function to enable putting objects in S3 buckets, but I get a task timed out after 3 seconds error when I try to test it. What am I doing wrong?
Thanks

How to get CKEditor react component usable in ClojureScript

I cannot get CKEditor react component to work in ClojureScript project.
When I try to use the component
(ns simple.core
(:require [reagent.core :as reagent]
[re-frame.core :as rf]
[clojure.string :as str]
[simple.routes :as routes]
["ckeditor4-react" :as ck :default CKEditor]
))
...
[:> CKEditor]
I get
template.cljs:312 Uncaught Error: Assert failed: Expected React component in: [:> nil]
(in simple.core.ui)
(or (string? comp) (fn? comp))
at Object.reagent$impl$template$vec_to_elem [as vec_to_elem] (template.cljs:312)
at Object.reagent$impl$template$as_element [as as_element] (template.cljs:329)
at template.cljs:403
at core.cljs:5598
at core.cljs:5598
at Object.cljs$core$IKVReduce$_kv_reduce$arity$3 (core.cljs:5602)
at Object.cljs$core$_kv_reduce [as _kv_reduce] (core.cljs:700)
at Object.cljs$core$reduce_kv [as reduce_kv] (core.cljs:2543)
at reagent$impl$template$make_element (template.cljs:401)
at Object.reagent$impl$template$native_element [as native_element] (template.cljs:285)
I have created a simple github project to illustrate.
https://github.com/madhat2r/shadow-re-frame-simple-example
If you clone it and follow instructions in the readme you can reproduce.
Thanks for your help!
Turns out it was an issue with the :require using :default you have to use
["ckeditor4-react" :as CKEditor]
...
[:> CKEditor {:onBeforeLoad (fn [ck] (set! (.-disableAutoInline ck) true))}]
Shadow-cljs: https://shadow-cljs.github.io/docs/UsersGuide.html#_about_default_exports
Note: the onBeforeLoad function above fixes a bug with current version. Left it for anyone who might come across it. https://github.com/ckeditor/ckeditor4-react/issues/57

ClojureScript - Ajax - Retrieving json

I have my dependencies
(ns test.core
(:require [reagent.core :as reagent :refer [atom]]
[ajax.core :refer [GET]]))
I then have my handler that handles the responses to my ajax call
(defn handle-response [response]
(println (type response))
(println film))
THe data from my call is JSON, in the browser it looks like this:
{"id":3,"name":"Sicario","star":"Emily Blunt","rating":5}
When I run the above Clojure, I see this:
cljs.core/PersistentArrayMap core.cljs:192:23
{id 3, name Sicario, star Emily Blunt, rating 5}
Is their a way for me to go from the PersistArrayMap, and destructure it into id, name, star and rating?
I tried
(let [film (js->clj (.parse js/JSON response) :keywordize-keys true)]
...)
Hoping I would get a map named film, but no avail!
I think maybe I could use (get-in), but can't get the syntax right for this either.
Thanks,
Got it,
I was missing :response-format :json and :keywords? true from my GET call
Now, it looks like this:
(GET (str "https://localhost:5001/api/films/" film-name)
{:response-format :json
:keywords? true
And it all works.

Clojure encode Joda DateTime with ring-json

With the following app:
; src/webapp/core.clj
(ns webapp.core
(:require [compojure.core :refer [defroutes GET]]
[ring.middleware.json :as mid-json]
[clj-time.jdbc]))
(defn foo [request]
{:body {:now (org.joda.time.DateTime/now)}})
(defroutes routes
(GET "/foo" [] foo))
(def app
(-> routes
(mid-json/wrap-json-response)))
Hitting the /foo endpoint gives me this error:
com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class org.joda.time.DateTime: 2017-10-21T03:38:16.207Z
Is there a simple way to get ring-json to encode the DateTime object? Do I have to write my own middleware to convert it to e.g. a string first? If so, how would I do that? (I've never written ring middleware before).
My project.clj has these dependencies FYI:
[[org.clojure/clojure "1.8.0"]
[org.clojure/java.jdbc "0.6.1"]
[ring/ring-jetty-adapter "1.4.0"]
[compojure "1.4.0"]
[ring/ring-json "0.4.0"]
[clj-time "0.14.0"]]
If you're using Cheshire to generate JSON, you can extend its protocol to handle serialization then it should "just work":
(extend-protocol cheshire.generate/JSONable
org.joda.time.DateTime
(to-json [dt gen]
(cheshire.generate/write-string gen (str dt))))
Additionally, the following modification made it work for projects that are using the time library tick.alpha.api. I was getting the error
#error {:cause Cannot JSON encode object of class:
class java.time.Instant: 2019-10-23T00:31:40.668Z
:via
[{:type com.fasterxml.jackson.core.JsonGenerationException
:message Cannot JSON encode object of class:
class java.time.Instant: 2019-10-23T00:31:40.668Z
:at [cheshire.generate$generate invokeStatic generate.clj 152]}]
Implementing the following in the file db/core.clj fixed the issue for me.
(extend-protocol cheshire.generate/JSONable
java.time.Instant
(to-json [dt gen]
(cheshire.generate/write-string gen (str dt))))