;; lib/dream/flash.sx — Dream-on-SX flash messages. ;; A single-request cookie store: messages added during one request are read on ;; the NEXT request, then the cookie is cleared. Depends on types.sx + session.sx ;; (shared cookie helpers). A message is {:category c :message m}. ;; ── cookie codec ─────────────────────────────────────────────────── ;; escape the field separators so categories/messages round-trip safely (define dr/flash-esc (fn (s) (replace (replace (replace s "%" "%25") "|" "%7C") "~" "%7E"))) (define dr/flash-unesc (fn (s) (replace (replace (replace s "%7E" "~") "%7C" "|") "%25" "%"))) (define dr/flash-encode (fn (msgs) (join "~" (map (fn (m) (str (dr/flash-esc (get m :category)) "|" (dr/flash-esc (get m :message)))) msgs)))) (define dr/flash-decode (fn (s) (if (= s "") (list) (map (fn (part) (let ((i (index-of part "|"))) {:message (dr/flash-unesc (substr part (+ i 1))) :category (dr/flash-unesc (substr part 0 i))})) (split s "~"))))) ;; ── mutable outbox cell ──────────────────────────────────────────── (define dr/flash-box (fn () (let ((items (list))) {:add (fn (x) (set! items (concat items (list x)))) :get (fn () items)}))) ;; ── middleware ───────────────────────────────────────────────────── (define dream-flash-cookie-name "dream.flash") (define dream-flash (fn (next) (fn (req) (let ((incoming (dr/flash-decode (or (dream-cookie req dream-flash-cookie-name) ""))) (box (dr/flash-box))) (let ((resp (next (assoc req :dream-flash {:box box :incoming incoming})))) (let ((out ((get box :get)))) (cond ((not (empty? out)) (dream-set-cookie resp dream-flash-cookie-name (dr/flash-encode out) {:path "/" :http-only true :same-site "Lax"})) ((not (empty? incoming)) (dream-drop-cookie resp dream-flash-cookie-name)) (else resp)))))))) ;; ── handler-facing API ───────────────────────────────────────────── (define dream-add-flash-message (fn (req category msg) (begin ((get (get (get req :dream-flash) :box) :add) {:message msg :category category}) req))) (define dream-flash-messages (fn (req) (get (get req :dream-flash) :incoming))) (define dream-flash-category (fn (m) (get m :category))) (define dream-flash-message (fn (m) (get m :message))) ;; convenience: only messages of a given category (define dream-flash-of (fn (req category) (filter (fn (m) (= (get m :category) category)) (dream-flash-messages req))))