realtime
WebSocket-based real-time communication with JWT authentication, message routing, and topic-based pub/sub.
Key namespaces
| Namespace | Purpose |
|---|---|
|
Pure: connection records, authorization, filtering |
|
Pure: message creation, routing logic |
|
Pure: JWT extraction, claims validation, permission checks |
|
Pure: topic subscription management |
|
Protocols: |
|
Service orchestrating core + adapters |
|
In-memory registry (atom-backed) |
|
Atom-backed pub/sub state management |
|
Ring/Jetty WebSocket adapter |
|
Test JWT verifier; the application supplies the real one |
Message routing types
| Type | Target | Example use case |
|---|---|---|
|
All connections |
System announcement |
|
Specific user-id |
Direct message or notification |
|
Users with a specific role |
Admin-only alert |
|
Specific connection-id |
Job progress update |
Usage
(require '[wagoe.realtime.ports :as ports])
;; Broadcast
(ports/send-to-all service {:type :announcement :text "Maintenance in 5 minutes"})
;; To specific user
(ports/send-to-user service user-id {:type :notification :data {...}})
;; Topic pub/sub
(ports/subscribe-to-topic pubsub conn-id "order:123")
(ports/publish-to-topic service "order:123" {:type :order-updated :payload {...}})
Server-side subscribers
A topic subscriber does not have to be a browser. subscribe-service
registers an in-process function, so the same publish reaches connected
clients and code running in the server:
(def sub-id
(ports/subscribe-service pubsub "order:events"
(fn [message]
(create-notification! (:payload message)))))
(ports/publish-to-topic service "order:events"
{:type "created" :payload {:id 1}})
;; => 4 ; three open sockets and this handler
(ports/unsubscribe-service pubsub sub-id) ; => true
This is what lets an application use realtime as its internal event bus rather than running a second pub/sub alongside it for server-to-server messages.
What to know before relying on it:
-
Handlers are node-local. The function lives in one JVM and cannot be relayed, so it is registered on the node that will run it. Under the
:redisprovider the message is fanned out to every node and each node invokes its own handlers — a handler registered once therefore runs once, whichever node published. -
They run on the delivery thread. Keep them quick; hand slow or failure- prone work to
wagoe-jobs. -
A handler that throws is logged and skipped. It cannot stop the handlers after it, and it cannot stop delivery to sockets.
-
publish-to-topiccounts them. A topic with three handlers and no open sockets returns 3, not 0. -
Order between handlers is unspecified. If two subscribers must run in a fixed order, that is one subscriber calling two things.
Connection lifecycle
-
Client connects:
ws://host/ws?token=<jwt> -
Server verifies JWT via
IJWTVerifier -
Connection registered in registry
-
Client sends/receives messages
-
On disconnect: cleanup + unsubscribe from all topics
Testing
clojure -M:test :realtime