feat: add SSE live sync between clients
When any client pushes changes, the server broadcasts an SSE event to all connected clients watching that group. Clients immediately pull the latest data — no polling delay, no page reload. Server (~30 lines): - GET /events?group=G — SSE endpoint, holds connection open - On successful POST /sync, notify all SSE listeners for affected groups - Auth-aware: respects token and group permissions - Auto-cleanup on disconnect via http-kit on-close Client (~25 lines): - EventSource connects to /events?group=G on sync loop start - On SSE message, triggers do-pull! to fetch latest data - Auto-reconnects (browser EventSource built-in behavior) - Cleanup on destroy! Periodic polling remains as fallback (30s default). SSE provides sub-second sync for the common case.
This commit is contained in:
@@ -140,3 +140,40 @@
|
||||
(is (= uuid (:uuid pulled)))
|
||||
(is (= inst (:inst pulled)))
|
||||
(is (= {:a {:b 42}} (:nested pulled))))))
|
||||
|
||||
(deftest sse-notifies-on-push
|
||||
(testing "SSE endpoint sends event when a push succeeds"
|
||||
(let [;; Open SSE connection
|
||||
sse-req (-> (HttpRequest/newBuilder)
|
||||
(.uri (URI. (url "/events" "group=todo")))
|
||||
(.GET)
|
||||
(.build))
|
||||
;; Use a short timeout — we'll read what we get
|
||||
events (atom [])
|
||||
future (java.util.concurrent.CompletableFuture/supplyAsync
|
||||
(reify java.util.function.Supplier
|
||||
(get [_]
|
||||
(try
|
||||
(let [resp (.send client sse-req
|
||||
(HttpResponse$BodyHandlers/ofInputStream))
|
||||
is (.body resp)
|
||||
rdr (java.io.BufferedReader.
|
||||
(java.io.InputStreamReader. is "UTF-8"))]
|
||||
;; Read lines until we get a data event about "todo"
|
||||
(loop [deadline (+ (System/currentTimeMillis) 3000)]
|
||||
(when (< (System/currentTimeMillis) deadline)
|
||||
(when-let [line (.readLine rdr)]
|
||||
(swap! events conj line)
|
||||
(if (= line "data: todo")
|
||||
@events
|
||||
(recur deadline))))))
|
||||
(catch Exception _ @events)))))]
|
||||
;; Give SSE time to connect
|
||||
(Thread/sleep 200)
|
||||
;; Push a doc — should trigger SSE event
|
||||
(post-transit "/sync"
|
||||
[{:id "todo:sse-test" :value {:text "SSE!"} :base-version 0}])
|
||||
;; Wait for SSE reader to receive it
|
||||
(let [result (.get future 4 java.util.concurrent.TimeUnit/SECONDS)]
|
||||
(is (some #(= "data: todo" %) result)
|
||||
"SSE should have received a 'data: todo' event")))))
|
||||
|
||||
Reference in New Issue
Block a user