Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bb2f664

Browse files
committedJun 29, 2019
Initialize ignorabilis project
0 parents  commit bb2f664

File tree

5 files changed

+406
-0
lines changed

5 files changed

+406
-0
lines changed
 

‎.gitignore

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
*~
2+
*.iml
3+
*.jar
4+
*.class
5+
*.o
6+
*.pyc
7+
*.log
8+
*.a
9+
10+
project.clj
11+
.nrepl-history
12+
13+
/target
14+
/classes
15+
/.idea
16+
17+
/resources/public/js
18+
/checkouts
19+
/script/repl.clj
20+
/.lein-*
21+
/.nrepl-port
22+
/out
23+
/.repl

‎build.boot

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env boot
2+
3+
(set-env!
4+
:resource-paths #{"src/clj" "src/cljs"}
5+
:dependencies '[[adzerk/boot-cljs "1.7.48-5" :scope "test"]
6+
[adzerk/boot-cljs-repl "0.3.0" :scope "test"]
7+
[adzerk/boot-reload "0.4.12" :scope "test"]
8+
[pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
9+
[crisptrutski/boot-cljs-test "0.2.2-SNAPSHOT" :scope "test"]
10+
[com.cemerick/piggieback "0.2.1" :scope "test"]
11+
[weasel "0.7.0" :scope "test"]
12+
[org.clojure/tools.nrepl "0.2.10" :scope "test"]
13+
14+
[org.clojure/clojure "1.7.0"]
15+
[org.clojure/clojurescript "1.7.228"]
16+
17+
[environ "1.0.0"]
18+
[danielsz/boot-environ "0.0.4"]
19+
[org.danielsz/system "0.1.8"]
20+
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
21+
[com.taoensso/sente "1.5.0"]
22+
[http-kit "2.1.19"]
23+
;; [org.immutant/web "2.0.0-beta2"]
24+
[ring "1.4.0-RC1"]
25+
[ring/ring-defaults "0.1.5"]
26+
[bidi "2.0.9"]
27+
[compojure "1.3.4"]
28+
[hiccup "1.0.5"]
29+
[com.cognitect/transit-clj "0.8.275"]
30+
[com.cognitect/transit-cljs "0.8.220"]])
31+
32+
(require
33+
'[adzerk.boot-cljs :refer [cljs]]
34+
'[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]]
35+
'[adzerk.boot-reload :refer [reload]]
36+
'[crisptrutski.boot-cljs-test :refer [exit! test-cljs]]
37+
'[pandeiro.boot-http :refer [serve]]
38+
'[reloaded.repl :refer [init start stop go reset]]
39+
'[ignorabilis.system.core :refer [dev-system]]
40+
'[danielsz.boot-environ :refer [environ]]
41+
'[system.boot :refer [system run]])
42+
43+
(def version "0.1.0.0-SNAPSHOT")
44+
45+
(task-options!
46+
push {:ensure-branch nil}
47+
pom {:project 'ignorabilis
48+
:version version
49+
:description "Personal website"
50+
:url "https://bitbucket.org/irina-yaroslavova/ignorabilis"
51+
:license {"Eclipse Public License"
52+
"http://www.eclipse.org/legal/epl-v10.html"}})
53+
54+
(defn- generate-lein-project-file! [& {:keys [keep-project] :or {:keep-project true}}]
55+
(require 'clojure.java.io)
56+
(let [pfile ((resolve 'clojure.java.io/file) "project.clj")
57+
; Only works when pom options are set using task-options!
58+
{:keys [project version]} (:task-options (meta #'boot.task.built-in/pom))
59+
prop #(when-let [x (get-env %2)] [%1 x])
60+
head (list* 'defproject (or project 'boot-project) (or version "0.0.0-SNAPSHOT")
61+
(concat
62+
(prop :url :url)
63+
(prop :license :license)
64+
(prop :description :description)
65+
[:dependencies (get-env :dependencies)
66+
:repositories (get-env :repositories)
67+
:source-paths (vec (concat (get-env :source-paths)
68+
(get-env :resource-paths)))]))
69+
proj (pp-str head)]
70+
(if-not keep-project (.deleteOnExit pfile))
71+
(spit pfile proj)))
72+
73+
(deftask lein-generate
74+
"Generate a leiningen `project.clj` file.
75+
This task generates a leiningen `project.clj` file based on the boot
76+
environment configuration, including project name and version (generated
77+
if not present), dependencies, and source paths. Additional keys may be added
78+
to the generated `project.clj` file by specifying a `:lein` key in the boot
79+
environment whose value is a map of keys-value pairs to add to `project.clj`."
80+
[]
81+
(generate-lein-project-file! :keep-project true))
82+
83+
(deftask ignorabilis-build
84+
"Builds an uberjar of the ignorabilis project that can be run with java -jar"
85+
[]
86+
(comp
87+
(aot :namespace '#{ignorabilis.core})
88+
(pom :project 'ignorabilis
89+
:version version)
90+
(uber)
91+
(jar :main 'ignorabilis.core)
92+
(cljs :optimizations :advanced)
93+
(target)))
94+
95+
(deftask ignorabilis-dev []
96+
(comp
97+
(environ :env {:http-port 3019})
98+
(watch)
99+
(system :sys #'dev-system :auto-start true :hot-reload true :files ["core.clj"])
100+
(reload)
101+
(cljs :source-map true)
102+
(repl :server true)))
103+
104+
(defn -main [& args]
105+
(require 'ignorabilis.core)
106+
(apply (resolve 'ignorabilis.core/-main) args))

‎src/clj/ignorabilis/core.clj

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
(ns ignorabilis.core
2+
#_(:gen-class)
3+
(:require
4+
[clojure.string :as str]
5+
[ring.middleware.defaults :refer [site-defaults]]
6+
[compojure.core :as comp :refer (defroutes GET POST)]
7+
[compojure.route :as route]
8+
[hiccup.core :as hiccup]
9+
[clojure.core.async :as async :refer (<! <!! >! >!! put! chan go go-loop)]
10+
[taoensso.timbre :as timbre :refer (tracef debugf infof warnf errorf)]
11+
[taoensso.sente :as sente]
12+
13+
;;; ---> Choose (uncomment) a supported web server and adapter <---
14+
15+
[org.httpkit.server :as http-kit]
16+
17+
;; or
18+
19+
;; [immutant.web :as immutant]
20+
21+
[reloaded.repl :refer [system]]))
22+
23+
;;;; Logging config
24+
25+
;; (sente/set-logging-level! :trace) ; Uncomment for more logging
26+
27+
;;;; Server-side setup
28+
29+
30+
(defn landing-pg-handler [req]
31+
(hiccup/html
32+
[:h1 "Sente reference example"]
33+
[:p "An Ajax/WebSocket connection has been configured (random)."]
34+
[:hr]
35+
[:p [:strong "Step 1: "] "Open browser's JavaScript console."]
36+
[:p [:strong "Step 2: "] "Try: "
37+
[:button#btn1 {:type "button"} "chsk-send! (w/o reply)"]
38+
[:button#btn2 {:type "button"} "chsk-send! (with reply)"]]
39+
;;
40+
[:p [:strong "Step 3: "] "See browser's console + nREPL's std-out."]
41+
;;
42+
[:hr]
43+
[:h2 "Login with a user-id"]
44+
[:p "The server can use this id to send events to *you* specifically."]
45+
[:p [:input#input-login {:type :text :placeholder "User-id"}]
46+
[:button#btn-login {:type "button"} "Secure login!"]]
47+
[:script {:src "main.js"}] ; Include our cljs target
48+
))
49+
50+
(defn login!
51+
"Here's where you'll add your server-side login/auth procedure (Friend, etc.).
52+
In our simplified example we'll just always successfully authenticate the user
53+
with whatever user-id they provided in the auth request."
54+
[ring-request]
55+
(let [{:keys [session params]} ring-request
56+
{:keys [user-id]} params]
57+
(debugf "Login request: %s" params)
58+
{:status 200 :session (assoc session :uid user-id)}))
59+
60+
(defroutes my-routes
61+
(GET "/" req (landing-pg-handler req))
62+
;;
63+
(GET "/chsk" req ((:ring-ajax-get-or-ws-handshake (:sente system)) req))
64+
(POST "/chsk" req ((:ring-ajax-post (:sente system)) req))
65+
(POST "/login" req (login! req))
66+
;;
67+
(route/not-found "<h1>Page not found</h1>"))
68+
69+
(def my-ring-handler
70+
(let [ring-defaults-config
71+
(-> site-defaults
72+
(assoc-in [:static :resources] "/")
73+
(assoc-in [:security :anti-forgery] {:read-token (fn [req] (-> req :params :csrf-token))}))]
74+
75+
;; NB: Sente requires the Ring `wrap-params` + `wrap-keyword-params`
76+
;; middleware to work. These are included with
77+
;; `ring.middleware.defaults/wrap-defaults` - but you'll need to ensure
78+
;; that they're included yourself if you're not using `wrap-defaults`.
79+
;;
80+
(ring.middleware.defaults/wrap-defaults my-routes ring-defaults-config)))
81+
82+
;;;; Routing handlers
83+
84+
;; So you'll want to define one server-side and one client-side
85+
;; (fn event-msg-handler [ev-msg]) to correctly handle incoming events. How you
86+
;; actually do this is entirely up to you. In this example we use a multimethod
87+
;; that dispatches to a method based on the `event-msg`'s event-id. Some
88+
;; alternatives include a simple `case`/`cond`/`condp` against event-ids, or
89+
;; `core.match` against events.
90+
91+
(defmulti event-msg-handler :id) ; Dispatch on event-id
92+
;; Wrap for logging, catching, etc.:
93+
(defn event-msg-handler* [{:as ev-msg :keys [id ?data event]}]
94+
(debugf "Event: %s" event)
95+
(event-msg-handler ev-msg))
96+
97+
(defmethod event-msg-handler :default ; Fallback
98+
[{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]
99+
(let [session (:session ring-req)
100+
uid (:uid session)]
101+
(debugf "Unhandled event: %s" event)
102+
(when ?reply-fn
103+
(?reply-fn {:umatched-event-as-echoed-from-from-server event}))))
104+
105+
;; Add your (defmethod event-msg-handler <event-id> [ev-msg] <body>)s here...
106+
107+
108+
;;;; Example: broadcast server>user
109+
110+
;; As an example of push notifications, we'll setup a server loop to broadcast
111+
;; an event to _all_ possible user-ids every 10 seconds:
112+
(defn start-broadcaster! []
113+
(go-loop [i 0]
114+
(<! (async/timeout 10000))
115+
(println (format "Broadcasting server>user: %s" @(:connected-uids (:sente system))))
116+
(doseq [uid (:any @(:connected-uids (:sente system)))]
117+
((:chsk-send! (:sente system)) uid
118+
[:some/broadcast
119+
{:what-is-this "A broadcast pushed from server"
120+
:how-often "Every 10 seconds"
121+
:to-whom uid
122+
:i i}]))
123+
(recur (inc i))))
124+
125+
; Note that this'll be fast+reliable even over Ajax!:
126+
(defn test-fast-server>User-pushes []
127+
(doseq [uid (:any @(:connected-uids (:sente system)))]
128+
(doseq [i (range 100)]
129+
((:chsk-send! (:sente system)) uid [:fast-push/is-fast (str "hello " i "!!")]))))
130+
131+
(comment (test-fast-server>user-pushes))
132+
133+
;;;; Init
134+
135+
(defn start! []
136+
(start-broadcaster!))
137+
138+
;; #+clj (start!) ; Server-side auto-start disabled for LightTable, etc.
139+
(comment (start!)
140+
(test-fast-server>user-pushes))

‎src/clj/ignorabilis/system/core.clj

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(ns ignorabilis.system.core
2+
(:require
3+
[ignorabilis.core :refer [event-msg-handler* my-ring-handler]]
4+
[taoensso.sente.server-adapters.http-kit :refer (sente-web-server-adapter)]
5+
;; or
6+
;; [taoensso.sente.server-adapters.immutant :refer (sente-web-server-adapter)]
7+
;; Optional, for Transit encoding:
8+
[taoensso.sente.packers.transit :as sente-transit]
9+
[environ.core :refer [env]]
10+
[system.core :refer [defsystem]]
11+
(system.components
12+
[http-kit :refer [new-web-server]]
13+
[sente :refer [new-channel-sockets]])))
14+
15+
(defsystem dev-system
16+
[:web (new-web-server (Integer. (env :http-port)) my-ring-handler)
17+
:sente (new-channel-sockets event-msg-handler* sente-web-server-adapter)])
18+
19+
(defsystem prod-system
20+
[:web (new-web-server (Integer. (env :http-port)) my-ring-handler)
21+
:sente (new-channel-sockets event-msg-handler* sente-web-server-adapter
22+
{:packer (sente-transit/get-flexi-packer :edn)})])

‎src/cljs/ignorabilis/core.cljs

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
(ns ignorabilis.core
2+
(:require
3+
[clojure.string :as str]
4+
[cljs.core.async :as async :refer (<! >! put! chan)]
5+
[taoensso.encore :as enc :refer (tracef debugf infof warnf errorf)]
6+
[taoensso.sente :as sente :refer (cb-success?)]
7+
;; Optional, for Transit encoding:
8+
[taoensso.sente.packers.transit :as sente-transit])
9+
(:require-macros
10+
[cljs.core.async.macros :as asyncm :refer (go go-loop)]))
11+
12+
;;;; Logging config
13+
14+
;; (sente/set-logging-level! :trace) ; Uncomment for more logging
15+
;;;; Packer (client<->server serializtion format) config
16+
(def packer (sente-transit/get-flexi-packer :edn))
17+
;; (def packer :edn) ; Default packer (no need for Transit dep)
18+
19+
(debugf "ClojureScript appears to have loaded correctly.")
20+
(let [rand-chsk-type (if (>= (rand) 0.5) :ajax :auto)
21+
22+
{:keys [chsk ch-recv send-fn state]}
23+
(sente/make-channel-socket! "/chsk" ; Note the same URL as before
24+
{:type rand-chsk-type})]
25+
(debugf "Randomly selected chsk type: %s" rand-chsk-type)
26+
(def chsk chsk)
27+
(def ch-chsk ch-recv) ; ChannelSocket's receive channel
28+
(def chsk-send! send-fn) ; ChannelSocket's send API fn
29+
(def chsk-state state) ; Watchable, read-only atom
30+
)
31+
32+
;;;; Routing handlers
33+
34+
(defmulti event-msg-handler :id) ; Dispatch on event-id
35+
;; Wrap for logging, catching, etc.:
36+
(defn event-msg-handler* [{:as ev-msg :keys [id ?data event]}]
37+
(debugf "Event: %s" event)
38+
(event-msg-handler ev-msg))
39+
40+
(defmethod event-msg-handler :default ; Fallback
41+
[{:as ev-msg :keys [event]}]
42+
(debugf "Unhandled event: %s" event))
43+
44+
(defmethod event-msg-handler :chsk/state
45+
[{:as ev-msg :keys [?data]}]
46+
(if (= ?data {:first-open? true})
47+
(debugf "Channel socket successfully established!")
48+
(debugf "Channel socket state change: %s" ?data)))
49+
50+
(defmethod event-msg-handler :chsk/recv
51+
[{:as ev-msg :keys [?data]}]
52+
(debugf "Push event from server: %s" ?data))
53+
54+
(defmethod event-msg-handler :chsk/handshake
55+
[{:as ev-msg :keys [?data]}]
56+
(let [[?uid ?csrf-token ?handshake-data] ?data]
57+
(debugf "Handshake: %s" ?data)))
58+
59+
;; Add your (defmethod handle-event-msg! <event-id> [ev-msg] <body>)s here...
60+
61+
62+
;;;; Client-side UI
63+
64+
(when-let [target-el (.getElementById js/document "btn1")]
65+
(.addEventListener target-el "click"
66+
(fn [ev]
67+
(debugf "Button 1 was clicked (won't receive any reply from server)")
68+
(chsk-send! [:example/button1 {:had-a-callback? "nope"}]))))
69+
70+
(when-let [target-el (.getElementById js/document "btn2")]
71+
(.addEventListener target-el "click"
72+
(fn [ev]
73+
(debugf "Button 2 was clicked (will receive reply from server)")
74+
(chsk-send! [:example/button2 {:had-a-callback? "indeed"}] 5000
75+
(fn [cb-reply] (debugf "Callback reply: %s" cb-reply))))))
76+
77+
(when-let [target-el (.getElementById js/document "btn-login")]
78+
(.addEventListener target-el "click"
79+
(fn [ev]
80+
(let [user-id (.-value (.getElementById js/document "input-login"))]
81+
(if (str/blank? user-id)
82+
(js/alert "Please enter a user-id first")
83+
(do
84+
(debugf "Logging in with user-id %s" user-id)
85+
86+
;;; Use any login procedure you'd like. Here we'll trigger an Ajax
87+
;;; POST request that resets our server-side session. Then we ask
88+
;;; our channel socket to reconnect, thereby picking up the new
89+
;;; session.
90+
91+
(sente/ajax-call "/login"
92+
{:method :post
93+
:params {:user-id (str user-id)
94+
:csrf-token (:csrf-token @chsk-state)}}
95+
(fn [ajax-resp]
96+
(debugf "Ajax login response: %s" ajax-resp)
97+
(let [login-successful? true ; Your logic here
98+
]
99+
(if-not login-successful?
100+
(debugf "Login failed")
101+
(do
102+
(debugf "Login successful")
103+
(sente/chsk-reconnect! chsk))))))))))))
104+
105+
(def router_ (atom nil))
106+
(defn stop-router! [] (when-let [stop-f @router_] (stop-f)))
107+
(defn start-router! []
108+
(stop-router!)
109+
(reset! router_ (sente/start-chsk-router! ch-chsk event-msg-handler*)))
110+
111+
112+
(defn start! []
113+
(start-router!))
114+
115+
(start!)

0 commit comments

Comments
 (0)
Please sign in to comment.