Howto enable Fulcro Websockets

258 Views Asked by At

I have a basic question about how to get started with Fulcro and Websockets.

i) I started with the Fulcro lein template. ii) Then added the websocket client and server bits. iii) In my server, I also added a com.fulcrologic.fulcro.networking.websocket-protocols.WSListener to detect when a WS client is connecting.

Between the WSListener, and the browser's network console, I can see that the client is never making a WS connection.

  • How does Fulcro make the initial WS connection?
  • After that, how can I make server pushes to client?

client.cljs

(ns foo.client
  (:require [fulcro.client :as fc]
            [foo.ui.root :as root]
            [fulcro.client.network :as net]
            [fulcro.client.data-fetch :as df]
            [com.fulcrologic.fulcro.networking.websockets :as fws]
            [com.fulcrologic.fulcro.application :as app]))

;; Neither this nor the below ":websocket (fws/fulcro-websocket-remote {})" works
;; (defonce app (app/fulcro-app {:remotes {:remote (fws/fulcro-websocket-remote {})}}))

(defonce SPA (atom nil))

(defn mount [] (reset! SPA (fc/mount @SPA root/Root "app")))
(defn start [] (mount))

(def secured-request-middleware
  ;; The CSRF token is embedded via server_components/html.clj
  (->
    (net/wrap-csrf-token (or js/fulcro_network_csrf_token "TOKEN-NOT-IN-HTML!"))
    (net/wrap-fulcro-request)))

(defn ^:export init []
  (reset! SPA (fc/make-fulcro-client
                {:client-did-mount (fn [foo]
                                     (df/load foo :all-users root/User))
                 ;; This ensures your client can talk to a CSRF-protected server.
                 ;; See middleware.clj to see how the token is embedded into the HTML
                 :networking       {:remote (net/fulcro-http-remote
                                              {:url                "/api"
                                               :request-middleware secured-request-middleware})
                                    :websocket (fws/fulcro-websocket-remote {})}}))
  (start))

middleware.cljs

(defrecord FooWSListener []
  WSListener
  (client-added [this ws-net cid]
    (println (str "Listener for dealing with client added events." [ws-net cid])))
  (client-dropped [this ws-net cid]
    (println (str "listener for dealing with client dropped events." [ws-net cid]))))

(def foo-ws-listener (->FooWSListener))

(def websockets' (atom nil))
(defn query-parser [env query] )

(defstate middleware
  :start
  (let [websockets (fws/start! (fws/make-websockets
                                 query-parser
                                 {:http-server-adapter (get-sch-adapter)
                                  :parser-accepts-env? true
                                  ;; See Sente for CSRF instructions
                                  :sente-options       {:csrf-token-fn nil}}))
        defaults-config (:ring.middleware/defaults-config config)
        legal-origins   (get config :legal-origins #{"localhost"})]

    (fwsp/add-listener websockets foo-ws-listener)
    (reset! websockets' websockets)

    (-> not-found-handler
        (wrap-api "/api")
        (fws/wrap-api websockets)
        server/wrap-transit-params
        server/wrap-transit-response
        (wrap-html-routes)
        (wrap-defaults defaults-config)
        wrap-gzip)))
1

There are 1 best solutions below

3
On BEST ANSWER

Since fulcro-websockets 3.1.0 the websocket connection is made on the first data transfer via websocket remote.

If you want to force the connection, you can do that by sending any mutation over the remote:

(:require [com.fulcrologic.fulcro.mutations :refer [defmutation]
          [com.fulcrologic.fulcro.components :as comp])

(defmutation connect-socket [_]
    (websocket [_] true))

(comment
    ;; trigger it via repl or a button handler
    (comp/transact! foo.client/SPA `[(connect-socket {})]

Once you make a connection, you can make a push from server like this:

(:require [com.fulcrologic.fulcro.networking.websocket-protocols :refer [push]])

(let [client-uid (-> @(:connected-uids websockets')
                     :any
                     first)]
    (push websockets' client-uid :foo-topic {:foo "bar"}))

To receive that on the client, you're going to need a :push-handler defined on the websocket remote:

(defn push-handler [{:keys [topic msg] :as data}]
    (log/info "push-handler received: " data))

;; optionally you can listen for websocket state changes
(defn state-callback [before after]
    (log/info "state-callback: " {:before before
                                  :after after}))

(defn ^:export init []


 (reset! SPA (fc/make-fulcro-client
                {:client-did-mount (fn [foo]
                                     (df/load foo :all-users root/User))
                 :remotes          {:remote (net/fulcro-http-remote
                                              {:url                "/api"
                                               :request-middleware secured-request-middleware})
                                    :websocket (fws/fulcro-websocket-remote
                                                   {:push-handler push-handler
                                                    :state-callback state-callback})}}))
  (start))

BTW since you're using template provided mount, you can use it to handle websockets' on the server:

(defstate websockets'
  :start
  (fws/start! (fws/make-websockets
               query-parser
               {:http-server-adapter (get-sch-adapter)
                :parser-accepts-env? true})))

This way you can avoid this line: (reset! websockets' websockets)