March Docs

Bastion.ChannelServer

lib/channel_server.march — WebSocket server for Bastion Channels.

Bastion.ChannelServer multiplexes multiple topics over a single WebSocket connection, routing joins, incoming events, and leaves to user-defined channel handler modules. It integrates with Bastion.PubSub so that broadcasts from any handler are delivered to all subscribed connections.

── Wire protocol (JSON) ───────────────────────────────────────────────────────

Client → Server (incoming frames): {"topic":"room:42","event":"join", "payload":{}, "ref":"1"} {"topic":"room:42","event":"leave", "payload":{}, "ref":"2"} {"topic":"room:42","event":"msg:new","payload":{"body":"hi"},"ref":"3"} {"topic":"phoenix", "event":"heartbeat","payload":{}, "ref":"4"}

Server → Client (reply to join/leave/heartbeat): {"topic":"room:42","event":"reply","payload":{"status":"ok","response":{}},"ref":"1"} {"topic":"room:42","event":"reply","payload":{"status":"error","response":{"reason":"unauthorized"}},"ref":"1"}

Server → Client (broadcast / push): {"topic":"room:42","event":"msg:new","payload":{...},"ref":null}

── Usage ──────────────────────────────────────────────────────────────────────

let routes =
  Bastion.ChannelServer.new_routes()
  |> Bastion.ChannelServer.add_route("room:", MyApp.RoomChannel.join, MyApp.RoomChannel.handle_in)

-- In the server pipeline:
HttpServer.new(4000)
|> HttpServer.plug(Bastion.ChannelServer.plug(routes))
|> HttpServer.listen()

-- Or with a custom path:
|> HttpServer.plug(Bastion.ChannelServer.plug_at("/socket/websocket", routes))

Types

typeChannelRouteChannelRoute(cc) = ChannelRoute(#
typeChannelRoutesChannelRoutes(cc) = ChannelRoutes(List(ChannelRoute(cc)))#

Functions

fnadd_routeadd_route(#

Register a topic prefix with its channel handler functions.

prefix       — topic must start with this string (e.g. "room:", "")
join_fn      — fn(conn, topic, params_json) → Result(ChannelConn, String)
handle_in_fn — fn(conn, event, payload_json) → ChannelConn

The empty string prefix "\"\"" matches every topic — use it as a catch-all
after more specific prefixes.

    routes
    |> ChannelServer.add_route("room:",  MyApp.RoomChannel.join,  MyApp.RoomChannel.handle_in)
    |> ChannelServer.add_route("system:", MyApp.SysChannel.join,  MyApp.SysChannel.handle_in)
fnnew_routesnew_routes() : ChannelRoutes(cc)#

Create an empty route table.

fnplugplug(routes : ChannelRoutes(cc))#

Build a Conn → Conn plug that serves the channel WebSocket at the default path /_bastion/channel.

Requests to any other path pass through unchanged.
fnplug_atplug_at(path : String, routes : ChannelRoutes(cc))#

Build a Conn → Conn plug that serves the channel WebSocket at a custom path.

    HttpServer.plug(ChannelServer.plug_at("/socket/websocket", routes))