├── .gitignore ├── LICENSE ├── README.md ├── project.clj ├── resources └── public │ └── empty ├── src-client └── example │ ├── client.cljs │ └── node.cljs └── src-server └── example └── server.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | /logs 6 | /docs 7 | /doc 8 | *.jar 9 | *.class 10 | .lein* 11 | pom.xml* 12 | .env 13 | /resources/public/*.js 14 | /node_modules 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Sente reference example 2 | 3 | > 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. 4 | 5 | ## Instructions 6 | 7 | 1. Call `lein start` at your terminal, then point your browser at http://localhost:4000 8 | 2. Observe std-out (server log) and web page textarea (client log) 9 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.taoensso.examples/sente "1.10.0-SNAPSHOT" 2 | :description "Sente, node.js web-app example project" 3 | :url "https://github.com/ptaoussanis/sente" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo 7 | :comments "Same as Clojure"} 8 | :min-lein-version "2.3.3" 9 | :global-vars {*warn-on-reflection* true 10 | *assert* true} 11 | 12 | :dependencies 13 | [[org.clojure/clojure "1.8.0"] 14 | 15 | [org.clojure/clojurescript "1.9.671"] 16 | [org.clojure/core.async "0.3.443"] 17 | [org.clojure/tools.nrepl "0.2.13"] ; Optional, for Cider 18 | 19 | [com.taoensso/timbre "4.10.0"] 20 | [com.taoensso/sente "1.11.0"] ; <--- Sente 21 | 22 | [hiccups "0.3.0"] ; Optional, just for HTML 23 | 24 | ;;; ---> Choose (uncomment) a supported web server <--- 25 | [org.clojars.whamtet/dogfort "0.2.0-SNAPSHOT"] 26 | 27 | ;; Macchiato 28 | [bidi "2.1.2"] 29 | [macchiato/core "0.2.2"] 30 | [macchiato/env "0.0.6"] 31 | [macchiato/auth "0.0.1"] 32 | 33 | ;;; Transit deps optional; may be used to aid perf. of larger data payloads 34 | ;;; (see reference example for details): 35 | [com.cognitect/transit-cljs "0.8.239"]] 36 | 37 | :npm 38 | {:dependencies 39 | [[source-map-support "*"] 40 | 41 | ;; Express 42 | [express "*"] 43 | [express-ws "*"] 44 | [body-parser "*"] 45 | [cookie-parser "*"] 46 | [express-session "*"] 47 | [csurf "*"] 48 | 49 | ;; ws is needed for dogfort and express 50 | [ws "*"] 51 | 52 | ;; websocket is needed for the node.js client 53 | [websocket "*"]]} 54 | 55 | :plugins 56 | [[lein-pprint "1.1.2"] 57 | [lein-ancient "0.6.10"] 58 | [com.cemerick/austin "0.1.6"] 59 | [lein-cljsbuild "1.1.3"] 60 | [lein-shell "0.5.0"] 61 | [macchiato/lein-npm "0.6.3"] 62 | [cider/cider-nrepl "0.12.0"] ; Optional, for use with Emacs 63 | ] 64 | 65 | :cljsbuild 66 | {:builds 67 | [{:id :cljs-server 68 | :source-paths ["src-server"] 69 | :compiler {:main example.server 70 | :output-to "target/main.js" 71 | :output-dir "target/out" 72 | :target :nodejs 73 | ;;:optimizations :simple 74 | ;;:source-map "target/main.js.map" 75 | :optimizations :none 76 | :source-map true 77 | :pretty-print true}} 78 | {:id :cljs-client 79 | :source-paths ["src-client"] 80 | :compiler {:main example.client 81 | :output-to "resources/public/main.js" 82 | :optimizations :whitespace #_:advanced 83 | :pretty-print true}} 84 | {:id :node-client 85 | :source-paths ["src-client"] 86 | :compiler {:main example.node 87 | :output-to "target/node-client.js" 88 | :output-dir "target/node-out" 89 | :target :nodejs 90 | ;;:optimizations simple 91 | ;;:source-map "target/node.map.js" 92 | :optimizations :none 93 | :source-map true 94 | :pretty-print true}}]} 95 | 96 | ;; Call `lein start-repl` to get a (headless) development repl that you can 97 | ;; connect to with Cider+emacs or your IDE of choice: 98 | :aliases 99 | {"start" ["do" "clean," "npm" "install," "cljsbuild" "once," "shell" "node" "target/main.js"] 100 | "start-client" ["do" "clean," "npm" "install," "cljsbuild" "once," "shell" "node" "target/node-client.js"]} 101 | 102 | :repositories 103 | {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}) 104 | -------------------------------------------------------------------------------- /resources/public/empty: -------------------------------------------------------------------------------- 1 | empty 2 | -------------------------------------------------------------------------------- /src-client/example/client.cljs: -------------------------------------------------------------------------------- 1 | (ns example.client 2 | "Official Sente reference example: client" 3 | {:author "Peter Taoussanis (@ptaoussanis)"} 4 | 5 | (:require 6 | [clojure.string :as str] 7 | [cljs.core.async :as async :refer (! put! chan)] 8 | [taoensso.encore :as encore :refer ()] 9 | [taoensso.timbre :as timbre :refer-macros (tracef debugf infof warnf errorf)] 10 | [taoensso.sente :as sente :refer (cb-success?)] 11 | 12 | ;; Optional, for Transit encoding: 13 | [taoensso.sente.packers.transit :as sente-transit]) 14 | 15 | (:require-macros 16 | [cljs.core.async.macros :as asyncm :refer (go go-loop)])) 17 | 18 | ;; (timbre/set-level! :trace) ; Uncomment for more logging 19 | 20 | ;;;; Util for logging output to on-screen console 21 | 22 | (def output-el (.getElementById js/document "output")) 23 | (defn ->output! [fmt & args] 24 | (let [msg (apply encore/format fmt args)] 25 | (timbre/debug msg) 26 | (aset output-el "value" (str "• " (.-value output-el) "\n" msg)) 27 | (aset output-el "scrollTop" (.-scrollHeight output-el)))) 28 | 29 | (->output! "ClojureScript appears to have loaded correctly.") 30 | 31 | ;;;; Define our Sente channel socket (chsk) client 32 | 33 | (let [;; For this example, select a random protocol: 34 | rand-chsk-type (if (>= (rand) 0.5) :ajax :auto) 35 | _ (->output! "Randomly selected chsk type: %s" rand-chsk-type) 36 | 37 | ;; Serializtion format, must use same val for client + server: 38 | packer :edn ; Default packer, a good choice in most cases 39 | ;; (sente-transit/get-flexi-packer :edn) ; Experimental, needs Transit dep 40 | 41 | {:keys [chsk ch-recv send-fn state]} 42 | (sente/make-channel-socket-client! 43 | "/chsk" ; Must match server Ring routing URL 44 | {:type rand-chsk-type 45 | :packer packer})] 46 | 47 | (def chsk chsk) 48 | (def ch-chsk ch-recv) ; ChannelSocket's receive channel 49 | (def chsk-send! send-fn) ; ChannelSocket's send API fn 50 | (def chsk-state state) ; Watchable, read-only atom 51 | ) 52 | 53 | ;;;; Sente event handlers 54 | 55 | (defmulti -event-msg-handler 56 | "Multimethod to handle Sente `event-msg`s" 57 | :id ; Dispatch on event-id 58 | ) 59 | 60 | (defn event-msg-handler 61 | "Wraps `-event-msg-handler` with logging, error catching, etc." 62 | [{:as ev-msg :keys [id ?data event]}] 63 | (-event-msg-handler ev-msg)) 64 | 65 | (defmethod -event-msg-handler 66 | :default ; Default/fallback case (no other matching handler) 67 | [{:as ev-msg :keys [event]}] 68 | (->output! "Unhandled event: %s" event)) 69 | 70 | (defmethod -event-msg-handler :chsk/state 71 | [{:as ev-msg :keys [?data]}] 72 | (if (= ?data {:first-open? true}) 73 | (->output! "Channel socket successfully established!") 74 | (->output! "Channel socket state change: %s" ?data))) 75 | 76 | (defmethod -event-msg-handler :chsk/recv 77 | [{:as ev-msg :keys [?data]}] 78 | (->output! "Push event from server: %s" ?data)) 79 | 80 | (defmethod -event-msg-handler :chsk/handshake 81 | [{:as ev-msg :keys [?data]}] 82 | (let [[?uid ?csrf-token ?handshake-data] ?data] 83 | (->output! "Handshake: %s" ?data))) 84 | 85 | ;; TODO Add your (defmethod -event-msg-handler [ev-msg] )s here... 86 | 87 | ;;;; Sente event router (our `event-msg-handler` loop) 88 | 89 | (defonce router_ (atom nil)) 90 | (defn stop-router! [] (when-let [stop-f @router_] (stop-f))) 91 | (defn start-router! [] 92 | (stop-router!) 93 | (reset! router_ 94 | (sente/start-client-chsk-router! 95 | ch-chsk event-msg-handler))) 96 | 97 | ;;;; UI events 98 | 99 | (when-let [target-el (.getElementById js/document "btn1")] 100 | (.addEventListener target-el "click" 101 | (fn [ev] 102 | (->output! "Button 1 was clicked (won't receive any reply from server)") 103 | (chsk-send! [:example/button1 {:had-a-callback? "nope"}])))) 104 | 105 | (when-let [target-el (.getElementById js/document "btn2")] 106 | (.addEventListener target-el "click" 107 | (fn [ev] 108 | (->output! "Button 2 was clicked (will receive reply from server)") 109 | (chsk-send! [:example/button2 {:had-a-callback? "indeed"}] 5000 110 | (fn [cb-reply] (->output! "Callback reply: %s" cb-reply)))))) 111 | 112 | (defn btn-login-click [ev] 113 | (let [user-id (.-value (.getElementById js/document "input-login"))] 114 | (if (str/blank? user-id) 115 | (js/alert "Please enter a user-id first") 116 | (do 117 | (->output! "Logging in with user-id %s" user-id) 118 | 119 | ;;; Use any login procedure you'd like. Here we'll trigger an Ajax 120 | ;;; POST request that resets our server-side session. Then we ask 121 | ;;; our channel socket to reconnect, thereby picking up the new 122 | ;;; session. 123 | 124 | (sente/ajax-lite "/login" 125 | {:method :post 126 | :headers {:x-csrf-token (:csrf-token @chsk-state)} 127 | :params {:user-id (str user-id)}} 128 | (fn [ajax-resp] 129 | (->output! "Ajax login response: %s" ajax-resp) 130 | (let [login-successful? true ; Your logic here 131 | ] 132 | (if-not login-successful? 133 | (->output! "Login failed") 134 | (do 135 | (->output! "Login successful") 136 | (sente/chsk-reconnect! chsk)))))))))) 137 | 138 | (when-let [target-el (.getElementById js/document "btn-login")] 139 | (.addEventListener target-el "click" btn-login-click)) 140 | 141 | ;;;; Init stuff 142 | 143 | (defn start! [] (start-router!)) 144 | 145 | (defonce _start-once (start!)) 146 | -------------------------------------------------------------------------------- /src-client/example/node.cljs: -------------------------------------------------------------------------------- 1 | ;; Example node.js client from David Martin (https://github.com/DaveWM) 2 | 3 | ;; NOTE: This requires the js websocket library available through npm. 4 | ;; See project.clj. 5 | 6 | (ns example.node 7 | (:require 8 | [taoensso.sente :as sente] 9 | [taoensso.timbre :as timbre :refer-macros (tracef debugf infof warnf errorf)] 10 | [cljs.core.async :refer [! put! chan)] 10 | [taoensso.encore :as encore :refer ()] 11 | [taoensso.timbre :as timbre :refer-macros (tracef debugf infof warnf errorf)] 12 | [taoensso.sente :as sente] 13 | 14 | ;;; TODO: choose (uncomment) a supported web server and adapter 15 | ;;; You will also have to comment/uncomment the appropriate section below. 16 | ;; Dogfort 17 | ;; [dogfort.middleware.defaults :as defaults] 18 | ;; [dogfort.middleware.routes] 19 | [taoensso.sente.server-adapters.dogfort :refer (dogfort-adapter)] 20 | ;; [dogfort.http :refer (run-http)] 21 | 22 | ;; Express 23 | [taoensso.sente.server-adapters.express :as sente-express] 24 | 25 | ;; Macchiato 26 | [bidi.bidi :as bidi] 27 | [macchiato.server :as m-server] 28 | [macchiato.middleware.defaults :as m-defaults] 29 | [macchiato.util.response :as m-resp] 30 | [macchiato.middleware.resource :as m-resource] 31 | [macchiato.middleware.anti-forgery :as csrf] 32 | [macchiato.auth.backends.session :as m-session] 33 | [macchiato.auth.middleware :as m-auth] 34 | [taoensso.sente.server-adapters.macchiato :as sente-macchiato] 35 | 36 | ;; Optional, for Transit encoding: 37 | [taoensso.sente.packers.transit :as sente-transit]) 38 | (:require-macros 39 | [dogfort.middleware.routes-macros :refer (defroutes GET POST)] 40 | [hiccups.core :as hiccups :refer [html]] 41 | [cljs.core.async.macros :as asyncm :refer (go go-loop)])) 42 | 43 | (set! js/console.debug js/console.log) 44 | #_(timbre/set-level! :trace) 45 | 46 | (enable-console-print!) 47 | ;;(timbre/set-level! :trace) ; Uncomment for more logging 48 | 49 | ;;;; Ring handlers 50 | 51 | (defn not-found [ring-req] 52 | (-> (hiccups/html 53 | [:html 54 | [:body 55 | [:h2 (:uri ring-req) " was not found"]]]) 56 | (m-resp/not-found) 57 | (m-resp/content-type "text/html"))) 58 | 59 | (defn landing-pg-handler [ring-req] 60 | (debugf "Landing page handler") 61 | (-> [:html 62 | [:body 63 | [:h1 "Sente reference example"] 64 | [:p "An Ajax/WebSocket" [:strong " (random choice!)"] " has been configured for this example"] 65 | [:hr] 66 | [:p [:strong "Step 1: "] " try hitting the buttons:"] 67 | [:button#btn1 {:type "button"} "chsk-send! (w/o reply)"] 68 | [:button#btn2 {:type "button"} "chsk-send! (with reply)"] 69 | ;; 70 | 71 | [:p [:strong "Step 2: "] " observe std-out (for server output) and below (for client output):"] 72 | [:textarea#output {:style "width: 100%; height: 200px;"}] 73 | ;; 74 | 75 | [:hr] 76 | [:h2 "Step 3: try login with a user-id"] 77 | [:p "The server can use this id to send events to *you* specifically."] 78 | [:p 79 | [:input#input-login {:type :text :placeholder "User-id"}] 80 | [:button#btn-login {:type "button"} "Secure login!"]] 81 | ;; 82 | 83 | [:hr] 84 | [:h2 "Step 4: want to re-randomize Ajax/WebSocket connection type?"] 85 | [:p "Hit your browser's reload/refresh button"] 86 | [:script {:src "main.js"}] ; Include our cljs target 87 | ]] 88 | (hiccups/html) 89 | (m-resp/ok) 90 | (m-resp/content-type "text/html"))) 91 | 92 | (defn login-handler 93 | "Here's where you'll add your server-side login/auth procedure (Friend, etc.). 94 | In our simplified example we'll just always successfully authenticate the user 95 | with whatever user-id they provided in the auth request." 96 | [ring-req] 97 | (let [{:keys [session params]} ring-req 98 | {:keys [user-id]} params] 99 | (debugf "Login request: %s" params) 100 | {:status 200 :session (assoc session :uid user-id)})) 101 | 102 | ;; ************************************************************************* 103 | ;; vvvv UNCOMMENT FROM HERE FOR MACCHIATO vvvv 104 | 105 | 106 | (let [;; Serializtion format, must use same val for client + server: 107 | packer :edn ; Default packer, a good choice in most cases 108 | ;; (sente-transit/get-flexi-packer :edn) ; Experimental, needs Transit dep 109 | {:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn 110 | connected-uids]} 111 | (sente-macchiato/make-macchiato-channel-socket-server! {:packer packer})] 112 | (def ajax-post ajax-post-fn) 113 | (def ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn) 114 | (def ch-chsk ch-recv) ; ChannelSocket's receive channel 115 | (def chsk-send! send-fn) ; ChannelSocket's send API fn 116 | (def connected-uids connected-uids) ; Watchable, read-only atom 117 | ) 118 | 119 | (defn wrap-macchiato-res [handler] 120 | (fn [req res raise] 121 | (res (handler req)))) 122 | 123 | (defn routes [] 124 | ["/" {"" {:get (wrap-macchiato-res landing-pg-handler)} 125 | "chsk" {:get ajax-get-or-ws-handshake 126 | :post ajax-post 127 | :ws ajax-get-or-ws-handshake} 128 | "login" {:post (wrap-macchiato-res login-handler)}}]) 129 | 130 | 131 | 132 | (defn router [req res raise] 133 | (debugf "Request: %s" (select-keys req [:request-method :websocket? :uri :params :session])) 134 | (if-let [{:keys [handler route-params]} 135 | (bidi/match-route* (routes) (:uri req) req)] 136 | (handler (assoc req :route-params route-params) res raise) 137 | (res (not-found req)))) 138 | 139 | (def main-ring-handler 140 | (-> router 141 | (m-auth/wrap-authentication (m-session/session-backend)) 142 | (m-resource/wrap-resource "resources/public") 143 | (m-defaults/wrap-defaults m-defaults/site-defaults))) 144 | 145 | (defn start-selected-web-server! [ring-handler port] 146 | (infof "Starting Macchiato...") 147 | (let [options {:handler ring-handler 148 | :port port 149 | :websockets? true 150 | :on-success #(infof "Macchiato started on port %s" port)} 151 | server (m-server/start options)] 152 | (m-server/start-ws server ring-handler) 153 | {:port port 154 | :stop-fn #(.end server)})) 155 | 156 | 157 | ;; ^^^^ UNCOMMENT TO HERE FOR MACCHIATO ^^^^ 158 | ;; ************************************************************************* 159 | 160 | 161 | 162 | ;; ************************************************************************* 163 | ;; vvvv UNCOMMENT FROM HERE FOR DOGFORT vvvv 164 | 165 | ;; (let [;; Serializtion format, must use same val for client + server: 166 | ;; packer :edn ; Default packer, a good choice in most cases 167 | ;; ;; (sente-transit/get-flexi-packer :edn) ; Experimental, needs Transit dep 168 | 169 | ;; {:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn 170 | ;; connected-uids]} 171 | ;; (sente/make-channel-socket-server! dogfort-adapter 172 | ;; {:packer packer})] 173 | 174 | ;; (def ring-ajax-post ajax-post-fn) 175 | ;; (def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn) 176 | ;; (def ch-chsk ch-recv) ; ChannelSocket's receive channel 177 | ;; (def chsk-send! send-fn) ; ChannelSocket's send API fn 178 | ;; (def connected-uids connected-uids) ; Watchable, read-only atom 179 | ;; ) 180 | 181 | ;; (defn login! 182 | ;; "Here's where you'll add your server-side login/auth procedure (Friend, etc.). 183 | ;; In our simplified example we'll just always successfully authenticate the user 184 | ;; with whatever user-id they provided in the auth request." 185 | ;; [req res] 186 | ;; (let [req-session (aget req "session") 187 | ;; params (aget req "params") 188 | ;; user-id (aget params "user-id")] 189 | ;; (debugf "Login request: %s" params) 190 | ;; (aset req-session "uid" user-id) 191 | ;; (.send res "Success"))) 192 | 193 | 194 | ;; (defroutes ring-routes 195 | ;; (GET "/" ring-req (landing-pg-handler ring-req)) 196 | ;; (GET "/chsk" ring-req (ring-ajax-get-or-ws-handshake ring-req)) 197 | ;; (POST "/chsk" ring-req (ring-ajax-post ring-req)) 198 | ;; (POST "/login" ring-req (login-handler ring-req))) 199 | 200 | ;; (def main-ring-handler 201 | ;; (defaults/wrap-defaults ring-routes {:wrap-file "resources/public"})) 202 | 203 | ;; (defn start-selected-web-server! [ring-handler port] 204 | ;; (infof "Starting dogfort...") 205 | ;; (run-http ring-handler {:port port}) 206 | ;; {:stop-fn #(errorf "One does not simply stop dogfort...") 207 | ;; :port port}) 208 | 209 | ;; ^^^^ UNCOMMENT TO HERE FOR DOGFORT ^^^^ 210 | ;; ************************************************************************* 211 | 212 | 213 | ;; ************************************************************************* 214 | ;; vvvv UNCOMMENT FROM HERE FOR EXPRESS vvvv 215 | 216 | ;; (def http (nodejs/require "http")) 217 | ;; (def express (nodejs/require "express")) 218 | ;; (def express-ws (nodejs/require "express-ws")) 219 | ;; (def ws (nodejs/require "ws")) 220 | ;; (def cookie-parser (nodejs/require "cookie-parser")) 221 | ;; (def body-parser (nodejs/require "body-parser")) 222 | ;; (def csurf (nodejs/require "csurf")) 223 | ;; (def session (nodejs/require "express-session")) 224 | 225 | ;; (let [;; Serializtion format, must use same val for client + server: 226 | ;; packer :edn ; Default packer, a good choice in most cases 227 | ;; ;; (sente-transit/get-flexi-packer :edn) ; Experimental, needs Transit dep 228 | ;; {:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn 229 | ;; connected-uids]} 230 | ;; (sente-express/make-express-channel-socket-server! {:packer packer 231 | ;; :user-id-fn (fn [ring-req] (aget (:body ring-req) "session" "uid"))})] 232 | ;; (def ajax-post ajax-post-fn) 233 | ;; (def ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn) 234 | ;; (def ch-chsk ch-recv) ; ChannelSocket's receive channel 235 | ;; (def chsk-send! send-fn) ; ChannelSocket's send API fn 236 | ;; (def connected-uids connected-uids) ; Watchable, read-only atom 237 | ;; ) 238 | 239 | ;; (defn express-login-handler 240 | ;; "Here's where you'll add your server-side login/auth procedure (Friend, etc.). 241 | ;; In our simplified example we'll just always successfully authenticate the user 242 | ;; with whatever user-id they provided in the auth request." 243 | ;; [req res] 244 | ;; (let [req-session (aget req "session") 245 | ;; body (aget req "body") 246 | ;; user-id (aget body "user-id")] 247 | ;; (debugf "Login request: %s" user-id) 248 | ;; (aset req-session "uid" user-id) 249 | ;; (.send res "Success"))) 250 | 251 | ;; (defn routes [express-app] 252 | ;; (doto express-app 253 | ;; (.get "/" (fn [req res] (.send res (landing-pg-handler req)))) 254 | 255 | ;; (.ws "/chsk" 256 | ;; (fn [ws req next] 257 | ;; (ajax-get-or-ws-handshake req nil nil 258 | ;; {:websocket? true 259 | ;; :websocket ws}))) 260 | 261 | ;; (.get "/chsk" ajax-get-or-ws-handshake) 262 | ;; (.post "/chsk" ajax-post) 263 | ;; (.post "/login" express-login-handler) 264 | ;; (.use (.static express "resources/public")) 265 | ;; (.use (fn [req res next] 266 | ;; (warnf "Unhandled request: %s" (.-originalUrl req)) 267 | ;; (next))))) 268 | 269 | ;; (defn wrap-defaults [express-app routes] 270 | ;; (let [cookie-secret "the shiz"] 271 | ;; (doto express-app 272 | ;; (.use (fn [req res next] 273 | ;; (tracef "Request: %s" (.-originalUrl req)) 274 | ;; (next))) 275 | ;; (.use (session 276 | ;; #js {:secret cookie-secret 277 | ;; :resave true 278 | ;; :cookie {} 279 | ;; :store (.MemoryStore session) 280 | ;; :saveUninitialized true})) 281 | ;; (.use (.urlencoded body-parser 282 | ;; #js {:extended false})) 283 | ;; (.use (cookie-parser cookie-secret)) 284 | ;; (.use (csurf 285 | ;; #js {:cookie false})) 286 | ;; (routes)))) 287 | 288 | ;; (defn main-ring-handler [express-app] 289 | ;; ;; Can we even call this a ring handler? 290 | ;; (wrap-defaults express-app routes)) 291 | 292 | ;; (defn start-selected-web-server! [ring-handler port] 293 | ;; (infof "Starting express...") 294 | ;; (let [express-app (express) 295 | ;; express-ws-server (express-ws express-app)] 296 | 297 | ;; (ring-handler express-app) 298 | 299 | ;; (let [http-server (.listen express-app port)] 300 | ;; {:express-app express-app 301 | ;; :ws-server express-ws-server 302 | ;; :http-server http-server 303 | ;; :stop-fn #(.close http-server) 304 | ;; :port port}))) 305 | 306 | ;; ^^^^ UNCOMMENT TO HERE FOR EXPRESS ^^^^ 307 | ;; ************************************************************************* 308 | 309 | 310 | ;;;; Sente event handlers 311 | 312 | (defmulti -event-msg-handler 313 | "Multimethod to handle Sente `event-msg`s" 314 | :id ; Dispatch on event-id 315 | ) 316 | 317 | (defn event-msg-handler 318 | "Wraps `-event-msg-handler` with logging, error catching, etc." 319 | [{:as ev-msg :keys [id ?data event]}] 320 | (-event-msg-handler ev-msg)) 321 | 322 | (defmethod -event-msg-handler 323 | :default ; Default/fallback case (no other matching handler) 324 | [{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}] 325 | (let [session (:session ring-req) 326 | uid (:uid session)] 327 | (debugf "Unhandled event: %s" event) 328 | (when ?reply-fn 329 | (?reply-fn {:umatched-event-as-echoed-from-from-server event})))) 330 | 331 | ;; TODO Add your (defmethod -event-msg-handler [ev-msg] )s here... 332 | 333 | ;;;; Sente event router (our `event-msg-handler` loop) 334 | 335 | (defonce router_ (atom nil)) 336 | (defn stop-router! [] (when-let [stop-f @router_] (stop-f))) 337 | (defn start-router! [] 338 | (stop-router!) 339 | (reset! router_ 340 | (sente/start-server-chsk-router! 341 | ch-chsk event-msg-handler))) 342 | 343 | ;;;; Some server>user async push examples 344 | 345 | (defn start-example-broadcaster! 346 | "As an example of server>user async pushes, setup a loop to broadcast an 347 | event to all connected users every 10 seconds" 348 | [] 349 | (let [broadcast! 350 | (fn [i] 351 | (debugf "Broadcasting server>user: %s" @connected-uids) 352 | (doseq [uid (:any @connected-uids)] 353 | (chsk-send! uid 354 | [:some/broadcast 355 | {:what-is-this "An async broadcast pushed from server" 356 | :how-often "Every 10 seconds" 357 | :to-whom uid 358 | :i i}])))] 359 | 360 | (go-loop [i 0] 361 | (user-pushes 366 | "Quickly pushes 100 events to all connected users. Note that this'll be 367 | fast+reliable even over Ajax!" 368 | [] 369 | (doseq [uid (:any @connected-uids)] 370 | (doseq [i (range 100)] 371 | (chsk-send! uid [:fast-push/is-fast (str "hello " i "!!")])))) 372 | 373 | (comment (test-fast-server>user-pushes)) 374 | 375 | ;;;; Init stuff 376 | 377 | (defonce web-server_ (atom nil)) ; {:server _ :port _ :stop-fn (fn [])} 378 | (defn stop-web-server! [] (when-let [m @web-server_] ((:stop-fn m)))) 379 | (defn start-web-server! [& [port]] 380 | (stop-web-server!) 381 | (let [{:keys [stop-fn port] :as server-map} 382 | (start-selected-web-server! (var main-ring-handler) (or port 4000)) 383 | uri (str "http://localhost:" port "/")] 384 | (infof "Web server is running at `%s`" uri) 385 | (reset! web-server_ server-map))) 386 | 387 | (defn stop! [] (stop-router!) (stop-web-server!)) 388 | (defn start! [] (start-router!) (start-web-server!) (start-example-broadcaster!)) 389 | ;; (defonce _start-once (start!)) 390 | 391 | (defn -main [& _] 392 | (start!)) 393 | 394 | (set! *main-cli-fn* -main) ;; this is required 395 | 396 | (comment 397 | (start!) 398 | (test-fast-server>user-pushes)) 399 | --------------------------------------------------------------------------------