MessageChannel JSON exchange
Send ordinary JSON values through a local pair of native message ports.
Build and run
Requires Java 21+, Clojure CLI and the runtime named below. The example pins the compiler that supports native ^:async and await. Run these commands in a separate terminal from other examples.
git clone https://github.com/OUTCASTGEEK-TECH/fast-twitch.git
cd fast-twitch
git checkout a4eb73f96836d034c7a24fe09dc7d51e4c581f64
cd examples/07-native-transports
clojure -M:build ports
node target/ports.cjs
Expected result
The program prints MessageChannel with an explicit JSON codec passed after receiving the map {"values" [false nil 42]}.
Follow the ports
message-channel! creates both ports with :codec :json. The optional :on-event is attached to port2, so the program sends on port1. JSON encodes the ClojureScript value for the native port and decodes it into string-keyed maps at the receiver. The callback resolves a Promise with (:data event), not with the whole browser event object.
This is a local channel between contexts/ports; it is not TCP, an HTTP endpoint or persistent storage. Closing only port1 does not express ownership of port2. finally closes both handles, including the receiving port.
Exercises
Send a second value after arranging another consumer callback. Inspect :type, :data, :ports and the native event carrier in the event map. For a binary transferable use the native codec and an ArrayBuffer with {:transfer [buffer]}; ownership transfer means the sending context must stop using that buffer. The native codec does not automatically convert ClojureScript persistent maps into structured-clone objects; use native JavaScript data or select the JSON codec.
MessageChannel options · send! API.
Complete source and build files
The files below are copied without abbreviation from Fast-Twitch revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64. The walkthrough and API reference use the same revision.
Source: examples/07-native-transports/src/native_demo/ports.cljs
View this exact file on GitHub.
(ns native-demo.ports
(:require [cljs.core :refer [await]]
[fast-twitch.client.core :as client])
(:refer-global :only [Promise console]))
(defn ^:async -main
[]
(let [channel (atom nil)
received (Promise. (fn [resolve _]
(reset! channel (client/message-channel!
{:codec :json
:on-event #(resolve (:data %))}))))]
(try (client/send! (:port1 @channel) {"values" [false nil 42]})
(assert (= {"values" [false nil 42]} (await received)))
(.log console "MessageChannel with an explicit JSON codec passed")
(finally (client/close! (:port1 @channel)) (client/close! (:port2 @channel))))))
(set! *main-cli-fn* -main)
Source: examples/07-native-transports/deps.edn
View this exact file on GitHub.
{:aliases {:build {:main-opts ["-m" "native-demo.build"]}}
:deps {fast-twitch/fast-twitch {:local/root "../.."}
org.clojure/clojurescript {:git/sha "56a94ac13bfac22108e4d868dfd4763edaceb864"
:git/url "https://github.com/clojure/clojurescript"}}
:paths ["src" "dev"]}
Source: examples/07-native-transports/dev/native_demo/build.clj
View this exact file on GitHub.
(ns native-demo.build
(:require [cljs.build.api :as cljs]))
(def modes
#{"http" "websocket" "sse" "streams" "ports" "tcp-node" "tcp-deno" "tcp-bun"})
(defn -main
[mode]
(doseq [mode (if (= mode "all") (sort modes) [mode])]
(when-not (modes mode) (throw (ex-info "Choose an example" {:modes modes})))
(cljs/build "src"
{:main (symbol (str "native-demo." mode))
:target :nodejs
:output-to (str "target/" mode ".cjs")
:output-dir (str "target/" mode)
:optimizations :simple})
(println mode "COMPILED")))