Push rendered Hiccup to a page without writing JavaScript. The stream rides
the web session, so mount it as an ordinary web route, not :public. A client
names its topics in the URL; :authorize-topic decides which it may have, and
without it every topic is refused.
(require '[wagoe.realtime.shell.handlers.sse :as sse])
;; route, in the module's :web routes
["/orders/events"
{:get {:handler (sse/sse-handler svc
:authorize-topic (fn [request topic]
(may-see-order? (:user request) topic)))}}]
;; page
[:div {:hx-ext "sse" :sse-connect "/web/orders/events?topic=order:123"}
[:table [:tbody#order-rows {:sse-swap "message" :hx-swap "beforeend"}]]]
;; from a handler, a job, any replica
(sse/push-fragment! svc "order:123" [:tr [:td "Shipped"]])
(sse/push-fragment! svc "order:123" [:span#badge [:t :orders/count {:n 4}]] {:event "badge"})
[:t …] markers resolve in each subscriber’s locale. An element with
:hx-swap-oob swaps by id, so one push can update several places. Under the
:redis provider fragments cross the wire: keep them plain data.
Each open stream holds one server thread for as long as the page is open, and
writes a heartbeat every 15 seconds; a closed tab is cleaned up at the next one.
The :wagoe/realtime key caps streams per node with :max-streams (default
32, under Jetty’s 50 threads); a stream over the cap is told to retry in 10
seconds. A handler accepts at most :max-topics (16) topics per stream.
An open stream does not see a logout by itself. Pass :revalidate, asked every
heartbeat, and the stream closes once it answers false; the browser reconnects
and gets a 401:
(require '[wagoe.user.ports :as user-ports]
'[wagoe.user.shell.middleware :as user-mw])
(sse/sse-handler svc
:authorize-topic may-see-topic?
:revalidate (fn [request]
(when-let [session (some->> (user-mw/extract-session-token request)
(user-ports/validate-session user-service))]
(:active (user-ports/get-user-by-id user-service (:user-id session))))))
validate-session also refreshes the session’s last access, so an open page
keeps a sliding session alive.