some stuff kind of works now

This commit is contained in:
@s.roertgen 2024-08-02 18:28:22 +02:00
commit 36dad21f43
10 changed files with 545 additions and 0 deletions

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "ied",
"scripts": {
"ancient": "clojure -Sdeps '{:deps {com.github.liquidz/antq {:mvn/version \"RELEASE\"}}}' -m antq.core",
"watch": "npx shadow-cljs watch app browser-test karma-test",
"release": "npx shadow-cljs release app",
"build-report": "npx shadow-cljs run shadow.cljs.build-report app target/build-report.html"
},
"dependencies": {
"@noble/secp256k1": "^2.1.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"shadow-cljs": "2.26.2"
}
}

39
shadow-cljs.edn Normal file
View file

@ -0,0 +1,39 @@
{:nrepl {:port 8777}
:source-paths ["src" "test"]
:dependencies
[[reagent "1.1.1"]
[re-frame "1.4.2"]
[day8.re-frame/tracing "0.6.2"]
[bidi "2.1.6"]
[clj-commons/pushy "0.3.10"]
[binaryage/devtools "1.0.6"]
[day8.re-frame/re-frame-10x "1.9.3"]
[funcool/promesa "11.0.678"]
[nilenso/wscljs "0.2.0"]
]
:dev-http
{8280 "resources/public"
8290 "target/browser-test"}
:builds
{:app
{:target :browser
:output-dir "resources/public/js/compiled"
:asset-path "/js/compiled"
:modules
{:app {:init-fn ied.core/init}}
:devtools
{:preloads [day8.re-frame-10x.preload]}
:dev
{:compiler-options
{:closure-defines
{re-frame.trace.trace-enabled? true
day8.re-frame.tracing.trace-enabled? true}}}
:release
{:build-options
{:ns-aliases
{day8.re-frame.tracing day8.re-frame.tracing-stubs}}}}}}

4
src/ied/config.cljs Normal file
View file

@ -0,0 +1,4 @@
(ns ied.config)
(def debug?
^boolean goog.DEBUG)

24
src/ied/core.cljs Normal file
View file

@ -0,0 +1,24 @@
(ns ied.core
(:require
[reagent.dom :as rdom]
[re-frame.core :as re-frame]
[ied.events :as events]
[ied.routes :as routes]
[ied.views :as views]
[ied.config :as config]))
(defn dev-setup []
(when config/debug?
(println "dev mode")))
(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(let [root-el (.getElementById js/document "app")]
(rdom/unmount-component-at-node root-el)
(rdom/render [views/main-panel] root-el)))
(defn init []
(routes/start!)
(re-frame/dispatch-sync [::events/initialize-db])
(dev-setup)
(mount-root))

8
src/ied/db.cljs Normal file
View file

@ -0,0 +1,8 @@
(ns ied.db)
(def default-db
{:name "re-frame"
:show-add-event false
:events nil
:pk nil
:sockets []})

164
src/ied/events.cljs Normal file
View file

@ -0,0 +1,164 @@
(ns ied.events
(:require
[re-frame.core :as re-frame]
[ied.db :as db]
[day8.re-frame.tracing :refer-macros [fn-traced]]
[ied.subs :as subs]
[ied.nostr :as nostr]
[promesa.core :as p]
[wscljs.client :as ws]
[wscljs.format :as fmt]))
(re-frame/reg-event-db
::initialize-db
(fn-traced [_ _]
db/default-db))
(re-frame/reg-event-fx
::navigate
(fn-traced [_ [_ handler]]
{:navigate handler}))
(re-frame/reg-event-fx
::set-active-panel
(fn-traced [{:keys [db]} [_ active-panel]]
{:db (assoc db :active-panel active-panel)}))
;; Database Event?
(re-frame/reg-event-fx
::save-event
;; TODO if EOSE retrieved end connection identified by uri
;; TODO make events a set (?)
(fn-traced [{:keys [db]} [_ [uri event]]]
(println uri event)
(when (= (first event) "EVENT")
{:db (update db :events conj event)})))
(defn handlers
[ws-uri]
{:on-message (fn [e] (re-frame/dispatch [::save-event [ws-uri (-> (.-data e)
js/JSON.parse
(js->clj :keywordize-keys true))]]))
:on-open #(prn "Opening a new connection")
:on-close #(prn "Closing a connection")})
(defn create-socket
[uri]
(ws/create uri (handlers uri)))
(re-frame/reg-event-fx
::create-websocket
(fn-traced [{:keys [db]} [_ ws]]
{:db (update db :sockets conj (merge ws {:socket (create-socket (:uri ws))}))}))
(re-frame/reg-event-fx
::connect-to-websocket
(fn-traced [{:keys [db]} [_ _]]
{::connect-to-websocket-fx _})) ;; TODO identify the socket to connect to
(re-frame/reg-fx
::connect-to-websocket-fx
(fn [_]
(let [sockets (re-frame/subscribe [::subs/sockets])]
(println "Sockets: " @sockets)
(ws/send (:socket (first @sockets)) ["REQ" "4242" {:kinds [1 30142]
:limit 10}] fmt/json)
; (ws/close (:socket (first @sockets))) ;; should be handled otherwise (?)
)))
;; TODO use id to close socket
;; add connection status to socket
;; render connect / disconnect button based on status
(re-frame/reg-event-fx
::close-connection-to-websocket
(fn-traced [{:keys [db]} [_ _]]
{::close-connection-to-websocket-fx _}))
(re-frame/reg-fx
::close-connection-to-websocket-fx
(fn [_]
(let [sockets (re-frame/subscribe [::subs/sockets])]
(ws/close (:socket (first @sockets))))))
;; TODO
(re-frame/reg-event-fx
::send-to-relays
(fn-traced [cofx [_ signedEvent]]
{::send-to-relays-fx signedEvent}))
(re-frame/reg-fx
::send-to-relays-fx
(fn [signedEvent]
(let [sockets (re-frame/subscribe [::subs/sockets])]
(.log js/console (clj->js ["EVENT" signedEvent]))
(ws/send (:socket (first @sockets)) ["EVENT" signedEvent] fmt/json))))
(re-frame/reg-event-db
::update-websockets
(fn [db [_ sockets]]
(assoc db :sockets sockets)))
(re-frame/reg-event-fx
::remove-websocket
(fn-traced [{:keys [db]} [_ socket]]
{::remove-websocket-fx (:id socket)}))
(re-frame/reg-fx
::remove-websocket-fx
(fn [id]
(let [sockets (re-frame/subscribe [::subs/sockets])
filtered (filter #(not= id (:id %)) @sockets)] ;; TODO maybe this can also be done using the URI
(re-frame/dispatch [::update-websockets filtered]))))
(re-frame/reg-event-db
::toggle-show-add-event
(fn [db _]
(assoc db :show-add-event (not (:show-add-event db)))))
(re-frame/reg-event-fx
::publish-resource
[(re-frame/inject-cofx :now)]
(fn-traced [cofx [_ resource]]
(let [event {:kind 30142
:created_at (:now cofx)
:content "hello world"
::tags []
;;:tags [["author" "" (:author resource)]]
}]
{::publish-resource-fx event})))
(re-frame/reg-fx
::publish-resource-fx
(fn [resource]
(p/let [signedEvent (.nostr.signEvent js/window (clj->js resource))]
(re-frame/dispatch [::send-to-relays signedEvent]))))
(re-frame/reg-event-fx
::login-with-extension
(fn-traced [cofx [_ _]]
{::login-with-extension-fx _}))
(re-frame/reg-fx
::login-with-extension-fx
(fn [db _]
(p/let [pk (.nostr.getPublicKey js/window)]
(re-frame/dispatch [::save-pk pk]))))
(re-frame/reg-event-db
::save-pk
(fn-traced [db [_ pk]]
(assoc db :pk pk)))
(re-frame/reg-event-db
::logout
(fn-traced [db _]
(assoc db :pk nil)))
(re-frame/reg-cofx
:now
(fn [cofx _data] ;; _data unused
(assoc cofx :now (quot (.now js/Date) 1000))))
(comment
(quot (.now js/Date) 1000))

52
src/ied/nostr.cljs Normal file
View file

@ -0,0 +1,52 @@
(ns ied.nostr
(:require
[promesa.core :as p]
["@noble/secp256k1" :as secp]))
(defn event-to-serialized-json [m]
(let [event [0
(:pubkey m)
(:created_at m)
(:kind m)
(:tags m)
(:content m)]]
(js/JSON.stringify (clj->js event))))
(defn pad-start [s len pad]
(let [padding (apply str (repeat (- len (count s)) pad))]
(str padding s)))
(defn to-hex [b]
(pad-start (.toString b 16) 2 "0"))
(defn byte-array-to-hex [byte-array]
(apply str (map to-hex byte-array)))
(defn sha256 [text]
(p/let [encoder (new js/TextEncoder)
data (.encode encoder text)
hash (.crypto.subtle.digest js/window "SHA-256" data)
hashArray (.from js/Array (new js/Uint8Array hash))
byteArray (js/Uint8Array. hashArray)
hexArray (byte-array-to-hex byteArray)]
hexArray))
(defn signEvent [hashedEvent sk]
(.sign secp hashedEvent sk))
(comment
(.getPublicKey secp (.utils.randomPrivateKey secp)))
(comment
(p/let [event {:content "hello world"
:created_at 1722606842
:kind 1
:pubkey "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
:tags []}
serialized-event (event-to-serialized-json event)
id (sha256 serialized-event)]
(println serialized-event)
(println id)))

44
src/ied/routes.cljs Normal file
View file

@ -0,0 +1,44 @@
(ns ied.routes
(:require
[bidi.bidi :as bidi]
[pushy.core :as pushy]
[re-frame.core :as re-frame]
[ied.events :as events]))
(defmulti panels identity)
(defmethod panels :default [] [:div "No panel found for this route."])
(def routes
(atom
["/" {"" :home
"about" :about
"settings" :settings}]))
(defn parse
[url]
(bidi/match-route @routes url))
(defn url-for
[& args]
(apply bidi/path-for (into [@routes] args)))
(defn dispatch
[route]
(let [panel (keyword (str (name (:handler route)) "-panel"))]
(re-frame/dispatch [::events/set-active-panel panel])))
(defonce history
(pushy/pushy dispatch parse))
(defn navigate!
[handler]
(pushy/set-token! history (url-for handler)))
(defn start!
[]
(pushy/start! history))
(re-frame/reg-fx
:navigate
(fn [handler]
(navigate! handler)))

33
src/ied/subs.cljs Normal file
View file

@ -0,0 +1,33 @@
(ns ied.subs
(:require
[re-frame.core :as re-frame]))
(re-frame/reg-sub
::name
(fn [db]
(:name db)))
(re-frame/reg-sub
::active-panel
(fn [db _]
(:active-panel db)))
(re-frame/reg-sub
::sockets
(fn [db _]
(:sockets db)))
(re-frame/reg-sub
::events
(fn [db _]
(:events db)))
(re-frame/reg-sub
::show-add-event
(fn [db _]
(:show-add-event db)))
(re-frame/reg-sub
::pk
(fn [db _]
(:pk db)))

160
src/ied/views.cljs Normal file
View file

@ -0,0 +1,160 @@
(ns ied.views
(:require
[re-frame.core :as re-frame]
[ied.events :as events]
[ied.routes :as routes]
[ied.subs :as subs]
[reagent.core :as reagent]))
;; add resource form
(defn add-resource-form
[name uri author]
(let [s (reagent/atom {:name name
:uri uri
:author author})]
(fn []
[:form {:on-submit (fn [e]
(.preventDefault e)
;; do something with the state @s
)}
[:label {:for name} "Name: "]
[:input {:type :text :name :name
:value (:name @s)
:on-change (fn [e]
(swap! s assoc :name (-> e .-target .-value)))}]
[:label {:for uri} "Uri: "]
[:input {:type :text :name :uri
:value (:uri @s)
:on-change (fn [e]
(swap! s assoc :uri (-> e .-target .-value)))}]
[:label {:for uri} "Author: "]
[:input {:type :text :name :author
:value (:author @s)
:on-change (fn [e]
(swap! s assoc :author (-> e .-target .-value)))}]
[:button {:on-click #(re-frame/dispatch [::events/publish-resource {:name (:name @s)
:uri (:uri @s)
:author (:author @s)}])}
"Publish Resource"]])))
;; events
(defn events-panel []
(let [events (re-frame/subscribe [::subs/events])
show-add-event (re-frame/subscribe [::subs/show-add-event])]
[:div
[:p {:on-click #(re-frame/dispatch [::events/toggle-show-add-event])}
(if @show-add-event "X" "Add Resource!")]
(when @show-add-event
[add-resource-form])
[:p (str "Num of events: " (count @events))]
(if (> (count @events) 0)
(doall
(for [event @events]
; [:li (:content (nth event 2 {:content "hello"}))]
[:li {:key (:id event)} (get (nth event 2) :content "")]
))
[:p "no events there"])]))
;; relays
(defn add-relay-form
[name uri]
(let [s (reagent/atom {:name name
:uri uri})]
(fn []
[:form {:on-submit (fn [e]
(.preventDefault e)
;; do something with the state @s
)}
[:label {:for name} "Name: "]
[:input {:type :text :name :name
:value (:name @s)
:on-change (fn [e]
(swap! s assoc :name (-> e .-target .-value)))}]
[:label {:for uri} "Uri: "]
[:input {:type :text :name :uri
:value (:uri @s)
:on-change (fn [e]
(swap! s assoc :uri (-> e .-target .-value)))}]
[:button {:on-click #(re-frame/dispatch [::events/create-websocket {:name (:name @s)
:id (random-uuid)
:uri (:uri @s)}])}
"Add Relay"]])))
(defn relays-panel
[]
(let [sockets (re-frame/subscribe [::subs/sockets])]
[:div
[add-relay-form]
(when (> (count @sockets) 0)
[:ul
(doall
(for [socket @sockets]
[:li {:key (:id socket)}
[:span (:name socket)]
[:button {:on-click #(re-frame/dispatch [::events/connect-to-websocket])} "Load events"]
[:button {:on-click #(re-frame/dispatch [::events/close-connection-to-websocket])} "Disconnect"]
[:button {:on-click #(re-frame/dispatch [::events/remove-websocket socket])} "Remove relay"]]))])]))
;; Header
(defn header []
(let [pk (re-frame/subscribe [::subs/pk])]
[:div
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
"home"]
"|"
[:a {:on-click #(re-frame/dispatch [::events/navigate :about])}
"about"]
"|"
[:a {:on-click #(re-frame/dispatch [::events/navigate :settings])}
"settings"]
"|"
(if @pk
[:a {:on-click #(re-frame/dispatch [::events/logout])} "logout"]
[:a {:on-click #(re-frame/dispatch [::events/login-with-extension])} "login"])]))
;; Settings
(defn settings-panel []
[:div
[:h1 "Settings"]
[relays-panel]])
(defmethod routes/panels :settings-panel [] [settings-panel])
;; Home
(defn home-panel []
(let [name (re-frame/subscribe [::subs/name])
events (re-frame/subscribe [::subs/events])]
[:div
[:h1
(str "Hello from " @name ". This is the Home Page.")]
[events-panel]
[:p (count @events)]]))
(defmethod routes/panels :home-panel [] [home-panel])
;; about
(defn about-panel []
[:div
[:h1 "This is the About Page."]
[:div
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
"go to Home Page"]]])
(defmethod routes/panels :about-panel [] [about-panel])
;; main
(defn main-panel []
(let [active-panel (re-frame/subscribe [::subs/active-panel])]
[:div
[header]
(routes/panels @active-panel)]))
(comment
(.log js/console "Hello World"))