From c0216de766030e5e1db5863aab1bd59a86424574 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 13 Jun 2026 23:05:33 +0200 Subject: [PATCH] feat(membershipd): --ws-port wires the embedded NATS WebSocket listener Phase 0 left the WebsocketConfig field unwired; add --ws-port so membershipd can actually expose the browser data-plane transport. It reuses the data-plane TLS (wss:// when TLS is on, ws:// for a loopback dev stack) and the same --cors-origins allowlist that gates the control plane, so one flag pair opens both planes to the browser-native uniweb client (issue uniweb/0001). --- cmd/membershipd/main.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cmd/membershipd/main.go b/cmd/membershipd/main.go index a252e75d..b4912183 100644 --- a/cmd/membershipd/main.go +++ b/cmd/membershipd/main.go @@ -55,6 +55,7 @@ func main() { dbPath = flag.String("db", "./local_files/unibus.db", "SQLite database path") storeDir = flag.String("store-dir", "./local_files/blobs", "blob store directory") natsPort = flag.Int("nats-port", 4250, "embedded NATS listen port (when --nats-url empty)") + wsPort = flag.Int("ws-port", 0, "WebSocket listen port for browser clients (nats.ws); 0 = disabled. Enables the browser-native uniweb client (issue uniweb/0001)") natsStore = flag.String("nats-store", "./local_files/jetstream", "embedded JetStream store dir") busAuth = flag.String("bus-auth", "off", "control-plane auth rollout: off|soft|enforce (feature flag bus-auth)") corsOrigins = flag.String("cors-origins", "", "comma-separated CORS allowlist of browser origins permitted to call the control plane (e.g. http://localhost:5173,https://chat.example.com); empty = CORS off. Enables the browser-native uniweb client (issue uniweb/0001)") @@ -269,6 +270,24 @@ func main() { cfg.TLS = tlsCfg log.Printf("NATS TLS: ON (%s)", *tlsCert) } + if *wsPort > 0 { + // Expose a WebSocket listener so browser clients (uniweb via nats.ws) reach + // the data plane directly. It reuses the data-plane TLS (wss:// when TLS is + // on, ws:// for a loopback dev stack) and the same browser-origin allowlist + // as the control-plane CORS, so opening the data plane to the browser and + // opening the control plane to it are governed by one --cors-origins list. + scheme := "ws" + if cfg.TLS != nil { + scheme = "wss" + } + cfg.Websocket = &embeddednats.WebsocketConfig{ + Host: *bind, + Port: *wsPort, + TLS: cfg.TLS, + AllowedOrigins: splitRoutes(*corsOrigins), + } + log.Printf("NATS WebSocket: ON (%s://%s:%d)", scheme, *bind, *wsPort) + } ns, err = embeddednats.StartServer(cfg) if err != nil { log.Fatalf("start embedded nats: %v", err)