20 April 2020

Shared Websocket Sessions with Fulcro

Why doesn't my server remember who I am?

I'm only using a different transport!

So I was expecting this to work out of the box, or at the very least be documented, but I couldn't find anything regarding it, so I figured best to do it myself =)...

It's thankfully not that complicated, just add to the default template a new namespace src/main/app/server_components/session.clj:

(ns app.server-components.session
  (:require [mount.core :refer [defstate]]
            [ring.middleware.session.store :as store]
            [ring.middleware.session.memory :refer [memory-store]]))


(defstate mem-store
  :start (memory-store))

(defn ws-lookup [key]
  (store/read-session mem-store key))

(defn get-session [env]
  (condp #(get-in %2 %1) env
    [:request :session/key]  ;; websockets path
    (some-> env :request :session/key ws-lookup)
    [:ring/request]          ;; http path
    (some-> env :ring/request :session)
    nil))

This is basically going to be our shared session store, obviously you can persist this store if you want, but for simplicity we'll just use the default memory one.

Now we need to adjust our middleware file src/main/app/server_components/middleware.clj:

(ns app.server-components.middleware
  (:require
    ...
    [app.server-components.session :refer [mem-store]]  ;; <-- Now stick the new ns we made into it
    ...))

...

(defstate middleware
  :start
  (let [defaults-config (:ring.middleware/defaults-config config)
        legal-origins   (get config :legal-origins #{"localhost"})]
    (-> not-found-handler
      ...
      (wrap-defaults (assoc-in defaults-config [:session :store] mem-store))  ;; <-- Adjust this to take our new session store
      wrap-gzip)))

Finally, and this part is important, we need to change our src/main/config/defaults.edn:

:static    {}
:session   {}  ;; <-- this is set to true in the template
:security  {}

And we're done! Not much right? But now you can take the example template here, which is just a fork of the official fulcro template and have working sessions if you use a ws-remote =)...

You can use the master version of my fork, but it may have other stuff in there you don't want...

PS: If you keep losing your session at the REPL

I don't know about you but when I'm developing I live in the REPL/Editor workflow, but the setup above has one downside, every time you sync your editor with your REPL you can lose your session state, requiring frequent logins, gumming up your flow. Or just add ^{:on-reload :noop} as shown below to your defstate and that won't be a problem!

And if you really do want to clear the session, just call (mount/stop) (mount/start) and you're good to go.

(defstate ^{:on-reload :noop} mem-store
  :start (memory-store))
Tags: sessions beginner fulcro websockets