From 221f112f1babf621d6a973bb4751a7fa6564ee57 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Tue, 28 Feb 2023 18:48:30 +0100 Subject: [PATCH] [new] [Example] Update, improve reference example 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. --- example-project/README.md | 25 +++++++- example-project/project.clj | 6 +- example-project/src/example/client.cljs | 78 +++++++++++++++++-------- example-project/src/example/server.clj | 76 +++++++++++++++--------- 4 files changed, 128 insertions(+), 57 deletions(-) diff --git a/example-project/README.md b/example-project/README.md index 81fd4bc5..e29ac21f 100644 --- a/example-project/README.md +++ b/example-project/README.md @@ -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) \ No newline at end of file +### 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. \ No newline at end of file diff --git a/example-project/project.clj b/example-project/project.clj index 89c54a46..924b262c 100644 --- a/example-project/project.clj +++ b/example-project/project.clj @@ -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/"}) diff --git a/example-project/src/example/client.cljs b/example-project/src/example/client.cljs index 21e94fe7..7c394472 100644 --- a/example-project/src/example/client.cljs +++ b/example-project/src/example/client.cljs @@ -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 @@ -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 @@ -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) @@ -82,9 +86,13 @@ (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]}] @@ -92,7 +100,7 @@ (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 [ev-msg] )s here... @@ -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" @@ -184,5 +215,4 @@ ;;;; Init stuff (defn start! [] (start-router!)) - (defonce _start-once (start!)) diff --git a/example-project/src/example/server.clj b/example-project/src/example/server.clj index c5ed55c1..ffa72937 100644 --- a/example-project/src/example/server.clj +++ b/example-project/src/example/server.clj @@ -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 @@ -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 )) @@ -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)) @@ -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 [ev-msg] )s here... ;;;; Sente event router (our `event-msg-handler` loop) @@ -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))