;; lib/dream/websocket.sx — Dream-on-SX WebSockets. ;; dream-websocket wraps a (fn (ws) ...) handler into an ordinary handler that ;; returns a 101 upgrade response carrying the ws handler. The host detects the ;; upgrade, builds a ws backed by host IO, and runs the handler. The ws carries an ;; injectable io fn — a mock in-memory ws for tests, (perform op) in production. ;; Depends on types.sx. ;; ── upgrade response ─────────────────────────────────────────────── (define dream-websocket (fn (handler) (fn (req) {:websocket handler :body "" :headers {:connection "Upgrade" :upgrade "websocket"} :status 101}))) (define dream-websocket? (fn (resp) (and (dict? resp) (has-key? resp :websocket)))) (define dream-ws-handler (fn (resp) (get resp :websocket))) ;; ── ws operations (over an injectable io) ────────────────────────── (define dream-send (fn (ws msg) ((get ws :io) {:op "ws/send" :msg msg}))) (define dream-receive (fn (ws) ((get ws :io) {:op "ws/receive"}))) (define dream-close (fn (ws) ((get ws :io) {:op "ws/close"}))) (define dream-ws-open? (fn (ws) ((get ws :io) {:op "ws/open?"}))) (define dream-ws-broadcast (fn (wss msg) (for-each (fn (ws) (dream-send ws msg)) wss))) ;; production io: every op suspends to the host (define dream-ws-perform-io (fn (op) (perform op))) (define dream-ws-from-io (fn (io) {:io io})) ;; ── in-memory mock ws (tests + demos) ────────────────────────────── ;; incoming is a list of messages dream-receive will yield in order. (define dream-mock-ws (fn (incoming) (let ((inbox incoming) (outbox (list)) (closed false)) {:closed? (fn () closed) :outbox (fn () outbox) :io (fn (op) (cond ((= (get op :op) "ws/send") (begin (set! outbox (concat outbox (list (get op :msg)))) true)) ((= (get op :op) "ws/receive") (if (empty? inbox) nil (let ((m (first inbox))) (begin (set! inbox (rest inbox)) m)))) ((= (get op :op) "ws/close") (begin (set! closed true) true)) ((= (get op :op) "ws/open?") (not closed)) (else nil)))}))) ;; test/demo introspection (define dream-ws-sent (fn (ws) ((get ws :outbox)))) (define dream-ws-closed? (fn (ws) ((get ws :closed?)))) ;; drive a ws handler (from an upgrade response) against a ws (define dream-ws-run (fn (resp ws) ((dream-ws-handler resp) ws)))