Skip to content

Commit

Permalink
[new] [Example] Update, improve reference example
Browse files Browse the repository at this point in the history
Improvements include:

  - Add button to toggle min log level on client+server
  - Add buttons to simulate broken connections using [#422]
  - General improvements to layout and text, etc.
  • Loading branch information
ptaoussanis committed Mar 7, 2023
1 parent bfa4c72 commit 221f112
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 57 deletions.
25 changes: 22 additions & 3 deletions example-project/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
# Official Sente reference example

> This example dives into Sente's full functionality pretty quickly; it's probably more useful as a reference than a tutorial. Please see Sente's top-level README for a gentler intro.
This example dives into Sente's full functionality pretty quickly; it's probably more useful as a reference than a tutorial.

Please see Sente's [top-level README](https://github.com/ptaoussanis/sente) for a gentler introduction to Sente.

## Instructions

1. Call `lein start` at your terminal, should auto-open web browser
2. Observe std-out (server log) and web page textarea (client log)
### Without REPL

1. Call `lein start` at your terminal.
2. This will start a local HTTP server and auto-open a test page in your web browser.
3. Follow the instructions from that page.

### With REPL

1. Call `lein start-dev` at your terminal.
2. This will start a local [nREPL server](https://nrepl.org/nrepl/index.html) and print the server's details, e.g.:

> nREPL server started on port 61332 on host 127.0.0.1 - nrepl://127.0.0.1:61332
2. This will start a local HTTP server and auto-open a test page in your web browser.
3. Follow the instructions from that page.

3. Connect your dev environment to that nREPL server, e.g. `(cider-connect)` from Emacs.
4. Open the example's [`server.clj`](https://github.com/ptaoussanis/sente/blob/master/example-project/src/example/server.clj) file in your dev environment.
5. Eval `(example.server/start!)` to start a local HTTP server and auto-open a test page in your web browser.
6. Follow the instructions from that page.
6 changes: 3 additions & 3 deletions example-project/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@

:clean-targets ^{:protect false} ["resources/public/main.js"]

;; Call `lein start-repl` to get a (headless) development repl that you can
;; Call `lein start-dev` to get a (headless) development repl that you can
;; connect to with Cider+emacs or your IDE of choice:
:aliases
{"start-repl" ["do" "clean," "cljsbuild" "once," "repl" ":headless"]
"start" ["do" "clean," "cljsbuild" "once," "run"]}
{"start-dev" ["do" "clean," "cljsbuild" "once," "repl" ":headless"]
"start" ["do" "clean," "cljsbuild" "once," "run"]}

:repositories
{"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"})
78 changes: 54 additions & 24 deletions example-project/src/example/client.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@

;;;; Logging config

(sente/set-min-log-level! :info) ; Min log level for internal Sente namespaces
(timbre/set-ns-min-level! :info) ; Min log level for this namespace
(defn- set-min-log-level! [level]
(sente/set-min-log-level! level) ; Min log level for internal Sente namespaces
(timbre/set-ns-min-level! level) ; Min log level for this namespace
)

(set-min-log-level! :info)

;;;; Util for logging output to on-screen console

Expand All @@ -29,7 +33,7 @@
(aset output-el "value" (str (.-value output-el) "\n" msg))
(aset output-el "scrollTop" (.-scrollHeight output-el))))

(->output! "ClojureScript appears to have loaded correctly.")
(->output! "ClojureScript has successfully loaded")

;;;; Define our Sente channel socket (chsk) client

Expand All @@ -39,7 +43,7 @@

(if ?csrf-token
(->output! "CSRF token detected in HTML, great!")
(->output! "CSRF token NOT detected in HTML, default Sente config will reject requests"))
(->output! "**IMPORTANT** CSRF token NOT detected in HTML, default Sente config will reject requests!"))

(let [;; For this example, select a random protocol:
rand-chsk-type (if (>= (rand) 0.5) :ajax :auto)
Expand Down Expand Up @@ -82,17 +86,21 @@
(defmethod -event-msg-handler :chsk/state
[{:as ev-msg :keys [?data]}]
(let [[old-state-map new-state-map] (have vector? ?data)]
(if (:first-open? new-state-map)
(->output! "Channel socket successfully established!: %s" new-state-map)
(->output! "Channel socket state change: %s" new-state-map))))
(cond
;; Tip: look for {:keys [opened? closed? first-open?]} in `new-state-map` to
;; easily identify these commonly useful state transitions
(:first-open? new-state-map) (->output! "Channel socket FIRST OPENED: %s" new-state-map)
(:opened? new-state-map) (->output! "Channel socket OPENED: %s" new-state-map)
(:closed? new-state-map) (->output! "Channel socket CLOSED: %s" new-state-map)
:else (->output! "Channel socket state changed: %s" new-state-map))))

(defmethod -event-msg-handler :chsk/recv
[{:as ev-msg :keys [?data]}]
(->output! "Push event from server: %s" ?data))

(defmethod -event-msg-handler :chsk/handshake
[{:as ev-msg :keys [?data]}]
(let [[?uid ?csrf-token ?handshake-data] ?data]
(let [[?uid _ ?handshake-data first-handshake?] ?data]
(->output! "Handshake: %s" ?data)))

;; TODO Add your (defmethod -event-msg-handler <event-id> [ev-msg] <body>)s here...
Expand All @@ -112,45 +120,68 @@
(when-let [target-el (.getElementById js/document "btn1")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Button 1 was clicked (won't receive any reply from server)")
(chsk-send! [:example/button1 {:had-a-callback? "nope"}]))))
(->output! "Will send event to server WITH callback")
(chsk-send! [:example/button2 {:had-a-callback? "indeed"}] 5000
(fn [cb-reply] (->output! "Callback reply: %s" cb-reply))))))

(when-let [target-el (.getElementById js/document "btn2")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Button 2 was clicked (will receive reply from server)")
(chsk-send! [:example/button2 {:had-a-callback? "indeed"}] 5000
(fn [cb-reply] (->output! "Callback reply: %s" cb-reply))))))
(->output! "Will send event to server WITHOUT callback")
(chsk-send! [:example/button1 {:had-a-callback? "nope"}]))))

(when-let [target-el (.getElementById js/document "btn3")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Button 3 was clicked (will ask server to test rapid async push)")
(->output! "Will ask server to test rapid async push")
(chsk-send! [:example/test-rapid-push]))))

(when-let [target-el (.getElementById js/document "btn4")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Button 4 was clicked (will toggle async broadcast loop)")
(chsk-send! [:example/toggle-broadcast] 5000
(fn [cb-reply]
(when (cb-success? cb-reply)
(let [loop-enabled? cb-reply]
(if loop-enabled?
(->output! "Async broadcast loop now enabled")
(->output! "Async broadcast loop now disabled")))))))))
(->output! "Server async broadcast loop now ENABLED")
(->output! "Server async broadcast loop now DISABLED")))))))))

(when-let [target-el (.getElementById js/document "btn5")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Disconnecting...\n\n")
(sente/chsk-disconnect! chsk))))
(fn [ev]
(->output! "Disconnecting...\n\n")
(sente/chsk-disconnect! chsk))))

(when-let [target-el (.getElementById js/document "btn6")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Reconnecting...\n\n")
(sente/chsk-reconnect! chsk))))
(fn [ev]
(->output! "Reconnecting...\n\n")
(sente/chsk-reconnect! chsk))))

(when-let [target-el (.getElementById js/document "btn7")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Simulating basic broken connection (WITH close)...\n\n")
(sente/chsk-break-connection! chsk {:close-ws? true}))))

(when-let [target-el (.getElementById js/document "btn8")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Simulating basic broken connection (WITHOUT close)...\n\n")
(sente/chsk-break-connection! chsk {:close-ws? false}))))

(when-let [target-el (.getElementById js/document "btn9")]
(.addEventListener target-el "click"
(fn [ev]
(->output! "Will ask server to toggle minimum log level")
(chsk-send! [:example/toggle-min-log-level] 5000
(fn [cb-reply]
(if (cb-success? cb-reply)
(let [level cb-reply]
(set-min-log-level! level)
(->output! "New minimum log level (client+server): %s" level))
(->output! "Failed to toggle minimum log level: %s" cb-reply)))))))

(when-let [target-el (.getElementById js/document "btn-login")]
(.addEventListener target-el "click"
Expand Down Expand Up @@ -184,5 +215,4 @@
;;;; Init stuff

(defn start! [] (start-router!))

(defonce _start-once (start!))
76 changes: 49 additions & 27 deletions example-project/src/example/server.clj
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,14 @@

;;;; Logging config

(sente/set-min-log-level! :info) ; Min log level for internal Sente namespaces
(timbre/set-ns-min-level! :info) ; Min log level for this namespace
(defonce min-log-level_ (atom nil))

(defn- set-min-log-level! [level]
(sente/set-min-log-level! level) ; Min log level for internal Sente namespaces
(timbre/set-ns-min-level! level) ; Min log level for this namespace
(reset! min-log-level_ level))

(set-min-log-level! :info)

;;;; Define our Sente channel socket (chsk) server

Expand Down Expand Up @@ -75,38 +81,40 @@

(defn landing-pg-handler [ring-req]
(hiccup/html
[:h1 "Sente reference example"]
(let [csrf-token
;; (:anti-forgery-token ring-req) ; Also an option
(force anti-forgery/*anti-forgery-token*)]

[:div#sente-csrf-token {:data-csrf-token csrf-token}])
[:p "An Ajax/WebSocket" [:strong " (random choice!)"] " has been configured for this example"]
[:hr]
[:p [:strong "Step 1: "] " try hitting the buttons:"]

[:h3 "Sente reference example"]
[:p
[:button#btn1 {:type "button"} "chsk-send! (w/o reply)"]
[:button#btn2 {:type "button"} "chsk-send! (with reply)"]]
"For this example, a " [:i "random"] " " [:strong [:code ":ajax/:auto"]]
" connection mode has been selected (see " [:strong "client output"] ")."
[:br]
"To " [:strong "re-randomize"] ", hit your browser's reload/refresh button."]
[:ul
[:li [:strong "Server output:"] "" [:code "*std-out*"]]
[:li [:strong "Client output:"] " → Below textarea and/or browser console"]]
[:textarea#output {:style "width: 100%; height: 200px;" :wrap "off"}]

[:h4 "Controls"]
[:p
[:button#btn3 {:type "button"} "Test rapid server>user async pushes"]
[:button#btn1 {:type "button"} "chsk-send! (with reply)"] " "
[:button#btn2 {:type "button"} "chsk-send! (w/o reply)"] " "]
[:p
[:button#btn3 {:type "button"} "Rapid server>user async push"] " "
[:button#btn4 {:type "button"} "Toggle server>user async broadcast push loop"]]
[:p
[:button#btn5 {:type "button"} "Disconnect"]
[:button#btn6 {:type "button"} "Reconnect"]]
;;
[:p [:strong "Step 2: "] " observe std-out (for server output) and below (for client output):"]
[:textarea#output {:style "width: 100%; height: 200px;"}]
;;
[:hr]
[:h2 "Step 3: try login with a user-id"]
[:p "The server can use this id to send events to *you* specifically."]
[:button#btn5 {:type "button"} "Disconnect"] " "
[:button#btn6 {:type "button"} "Reconnect"] " "
[:button#btn7 {:type "button"} "Simulate break (with on-close)"] " "
[:button#btn8 {:type "button"} "Simulate break (w/o on-close)"]]
[:p [:button#btn9 {:type "button"} "Toggle min log level"]]

[:p "Log in with a " [:strong "user-id"] " below so that the server can directly address this user-id's connected clients:"]
[:p
[:input#input-login {:type :text :placeholder "User-id"}]
[:button#btn-login {:type "button"} "Secure login!"]]
;;
[:hr]
[:h2 "Step 4: want to re-randomize Ajax/WebSocket connection type?"]
[:p "Hit your browser's reload/refresh button"]
[:input#input-login {:type :text :placeholder "User-id"}] " "
[:button#btn-login {:type "button"} "← Log in with user-id"]]
[:script {:src "main.js"}] ; Include our cljs target
))

Expand Down Expand Up @@ -195,7 +203,7 @@
uid (:uid session)]
(timbre/debugf "Unhandled event: %s" event)
(when ?reply-fn
(?reply-fn {:umatched-event-as-echoed-from-server event}))))
(?reply-fn {:unmatched-event-as-echoed-from-server event}))))

(defmethod -event-msg-handler :example/test-rapid-push
[ev-msg] (test-fast-server>user-pushes))
Expand All @@ -205,6 +213,20 @@
(let [loop-enabled? (swap! broadcast-enabled?_ not)]
(?reply-fn loop-enabled?)))

(defmethod -event-msg-handler :example/toggle-min-log-level
[{:as ev-msg :keys [?reply-fn]}]
(let [new-level
(case @min-log-level_
:trace :debug
:debug :info
:info :warn
:warn :error
:error :trace
:trace)]

(set-min-log-level! new-level)
(?reply-fn new-level)))

;; TODO Add your (defmethod -event-msg-handler <event-id> [ev-msg] <body>)s here...

;;;; Sente event router (our `event-msg-handler` loop)
Expand Down Expand Up @@ -260,5 +282,5 @@
(defn -main "For `lein run`, etc." [] (start!))

(comment
(start!)
(start!) ; Eval this at REPL to start server via REPL
(test-fast-server>user-pushes))

0 comments on commit 221f112

Please sign in to comment.