Search code examples
websocketclojurereagent

Clojure Sente: Sente sends a follow up message to the client after page load and destroys the view


I'm quite confused about why sente is sending the client a message all on its own around 8 seconds after the page is initially loaded. What else is strange, is that if I quickly send a message to the server over the websocket before this happens, everything stabilizes and the view doesn't crash.

For reference, the front-end is clojurescript with reagent, and its a luminus project. For further reference, its pretty much exactly the sample application from chapter 5 of "web development with clojure".

I can tell that its the server pushing a message to the client that's causing the problem, I just don't know enough about Sente to understand why it would even be doing this.

Here's what I think the relevant code is:

Server side:

(defn save-message! [message]
  (if-let [errors (validate-message message)]
    {:errors errors}
    (do
      (db/save-message! message)
      message)))

(defn handle-message! [{:keys [id client-id ?data]}]
  (when (= id :guestbook/add-message)
    (let [response (-> ?data
                       (assoc :timestamp (java.util.Date.))
                       save-message!)]
      (if (:errors response)
        (chsk-send! client-id [:guestbook/error response])
        (doseq [uid (:any @connected-uids)]
          (chsk-send! uid [:guestbook/add-message response]))))))

Client side (with reagent):

(defn response-handler [messages fields errors]
  (fn [{[_ message] :?data}]
    (if-let [response-errors (:errors message)]
      (reset! errors response-errors)
      (do
        ;; Fires right before the view crashes!
        (.log js/console "response-handled")
        (reset! errors nil)
        (reset! fields nil)
        (swap! messages conj message)))))

(defn home []
  (let [messages (atom nil)
        fields (atom nil)
        errors (atom nil)]
    (ws/start-router! (response-handler messages fields errors))
    (get-messages messages)
    (fn []
      [:div
       [:div.row
        [:div.span12
         [message-list messages]]]
       [:div.row
        [:div.span12
         [message-form fields errors]]]])))

The problem is that when sente sends the message on its own there is no data to update the messages (or atleast that's my best guess), so the atom's fields become null and reagent (react.js) throws trying to diff and patch from the vdom.

If anyone knows what sente is doing it would be very much appreciated. This exact same set up works fine when you use Immutant's async socket support, and do a lot of the work yourself (serialize/deserialize, handle connections etc).

;;;;;;;;

As a follow up, I solved the issue by filtering for non nil messages:

(defn response-handler [messages fields errors]
  (fn [{[_ message] :?data}]
    (if-let [response-errors (:errors message)]
      (reset! errors response-errors)
      (when (not= message nil)
        (reset! errors nil)
        (reset! fields nil)
        (swap! messages conj message)))))

Still, this is kind of a bandage solution, it would be nice to know why Sente throws a message at me after the page loads if the socket isn't used immediately.


Solution

  • You can inspect what is happening by looking at the network tab in the developer tools; there should be a sub-tab for websocket frames.

    Sente sends some events on its own, and the event name (a keyword which is the first element of the event vector) is in the chsk namespace if I remember correctly. I believe that you should use some kind of dispatch on the event names anyway, and not assume that only one kind of event will arrive.

    In the context of re-frame, I have been filtering the unwanted events and dispatching the rest to the re-frame event loop. I guess that you could do something similar in luminus. On the server side, I have been using multimethods in a similar setting.