March Docs

Channel

Channel module: Phoenix-style real-time channels for March.

Channels provide multiplexed bidirectional messaging over a single transport (WebSocket, long-poll, …). Each channel join creates a green thread that calls user-defined callbacks:

join/3        — called on join attempt; returns Ok(state) or Err(reason)
handle_in/4   — called when the client sends an event
handle_info/3 — called when the channel process receives an actor message
leave/3       — called on leave / disconnect

Types defined here: Socket — per-connection state (topic, assigns, pids) HandleResult — return type for channel callbacks ChannelMsg — JSON wire message (topic/event/payload/ref/join_ref) ChannelRoute — pattern → handler association used by the router LeaveReason — reason a channel is leaving

Helper functions (usable inside channel handlers): reply/3 — reply to client + update state noreply/1 — no reply, update state stop/2 — stop channel with reason assign/3 — add/update a key in socket.assigns push/3 — push a message to this specific client broadcast/3 — broadcast to all topic subscribers broadcast_from/3 — broadcast to all subscribers except self

Wire format (JSON over WebSocket): { "topic": "room:lobby", "event": "new_msg", "payload": {...}, "ref": "1", "join_ref": "0" }

Topic pattern matching (ChannelRoute): "room:*" matches "room:lobby", "room:42", etc. "notify:" matches exactly "notify:"

See also: channel_server.march (runtime loop), presence.march.

Types

typeSocketSocket = Socket(#

Socket: per-connection state for one channel join. topic — the joined topic, e.g. "room:lobby" channel_pid — the channel process (green thread) pid transport_pid — the underlying transport (WS frame loop) pid assigns — user key-value bag; set with Channel.assign/3 join_ref — the ref string from the original join message

typeHandleResultHandleResult(a) = Reply(String, String, a) | NoReply(a) | Stop(String, a)#

Return type for channel callbacks (handle_in, handle_info, join). Reply(status, payload, state) — reply to client; status is ":ok" or ":error" NoReply(state) — no reply; just update channel state Stop(reason, state) — close this channel with a reason string

typeChannelMailboxChannelMailbox =#

Messages that a channel process can receive in its mailbox. Defined here so they are available to push/broadcast helpers above and to ChannelServer/ChannelSocket which reference them.

typePubSubMsgPubSubMsg(a) =#

Messages that a PubSub shard actor can receive.

typeBroadcastMsgBroadcastMsg = BroadcastMsg(String, String)#

Wire message sent to subscribers on broadcast.

typeLeaveReasonLeaveReason = NormalLeave | Disconnect | Kicked(String)#

Reason a channel is leaving. NormalLeave — the client explicitly left Disconnect — the transport closed Kicked(msg) — server-initiated close with a message

typeChannelRouteChannelRoute = ChannelRoute(String, String)#

A ChannelRoute maps a topic pattern to a handler module name. pattern — e.g. "room:*" or "notify:user" handler — the name of the channel handler module (as a string)

typeChannelMsgChannelMsg = ChannelMsg(String, String, String, String, String)#

A channel message as it travels over the wire (JSON over WebSocket). Fields mirror the Phoenix channel protocol: join_ref — ref of the join message (correlates replies) ref — per-message ref (for request-reply correlation) topic — the topic string, e.g. "room:lobby" event — event name, e.g. "new_msg" or "phx_join" payload — JSON-encoded payload string

Functions

fnassignassign(sock, key, value)#

Return a socket with key set to value (both Strings). The pair is prepended, so it shadows any earlier value for the same key; the old pair stays in socket_assigns.

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(sock, "user", "alice"), "user")
Some("alice")
fnbroadcastbroadcast(sock, event, payload)#

Broadcast event+payload to ALL subscribers of the socket's topic (including self). Casts ChannelBroadcast(topic, event, payload) to the socket's own channel pid, whose loop is expected to route it through the PubSub shard. Requires actor runtime.

Channel.broadcast(sock, "new_msg", "{\"body\":\"hi\"}")
fnbroadcast_frombroadcast_from(sock, event, payload)#

Broadcast event+payload to all subscribers of the socket's topic EXCEPT the sender (this channel process). Casts ChannelBroadcastFrom(topic, channel_pid, event, payload) to the socket's own channel pid. Requires actor runtime.

Channel.broadcast_from(sock, "typing", "{}")
fnchannel_routechannel_route(pattern, handler_name)#

A route sending topics that match pattern to the handler named handler_name. A pattern ending in * matches by prefix at a segment boundary (see PubSub.topic_matches); any other pattern matches exactly.

Examples

march> Channel.route_pattern(Channel.channel_route("room:*", "RoomChannel"))
"room:*"
fnclose_msgclose_msg(join_ref, topic)#

A phx_close message for topic, with an empty ref and payload {} (sent when the channel is closed).

Examples

march> Channel.serialize(Channel.close_msg("0", "room:lobby"))
"{\"join_ref\":\"0\",\"ref\":\"\",\"topic\":\"room:lobby\",\"event\":\"phx_close\",\"payload\":{}}"
fnerror_msgerror_msg(join_ref, ref, topic, reason)#

A phx_error message for the client's ref on topic, with payload {"reason":<reason>} (sent on join rejection or handler Stop).

Examples

march> Channel.msg_payload(Channel.error_msg("0", "1", "room:lobby", "unauthorized"))
"{\"reason\":\"unauthorized\"}"
fnget_assignget_assign(sock, key)#

The value most recently assigned to key, as Some(value), or None when the key was never assigned. O(number of assigns).

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(sock, "user", "alice"), "user")
Some("alice")
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(Channel.assign(sock, "user", "alice"), "user", "bob"), "user")
Some("bob")
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(sock, "user")
None
fnmake_msgmake_msg(join_ref, ref, topic, event, payload)#

A wire message from its five fields, in wire order: join_ref, ref, topic, event, payload (a JSON value as text).

Examples

march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_event(cm)
"new_msg"
fnmsg_eventmsg_event(m)#

The event field of a message, e.g. "phx_join" or an application event.

Examples

march> Channel.msg_event(Channel.close_msg("0", "room:lobby"))
"phx_close"
fnmsg_join_refmsg_join_ref(m)#

The join_ref field of a message.

Examples

march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_join_ref(cm)
"0"
fnmsg_payloadmsg_payload(m)#

The payload of a message: JSON text, kept verbatim.

Examples

march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_payload(cm)
"{}"
fnmsg_refmsg_ref(m)#

The per-message ref field (the client correlates replies by it).

Examples

march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_ref(cm)
"1"
fnmsg_topicmsg_topic(m)#

The topic field of a message.

Examples

march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_topic(cm)
"room:lobby"
fnnew_socketnew_socket(topic, channel_pid, transport_pid, join_ref)#

A socket for one channel join on topic, served by channel_pid over transport_pid, with no assigns and the given join_ref.

Examples

march> Channel.socket_topic(Channel.new_socket("room:42", 1, 2, "7"))
"room:42"
fnnoreplynoreply(st)#

NoReply(st): send nothing back to the client and continue with st.

Examples

march> Channel.noreply(5)
NoReply(5)
fnparseparse(json_str)#

Parse a JSON channel message string into a ChannelMsg. Returns Some(msg) on success, None on parse failure. Performs minimal parsing: extracts the five fields using string search, not a JSON parser, so it only accepts the shape serialize produces:

  • each string field must appear as "key":"value" with no space after the
colon; a `null` field (Phoenix sends `"join_ref":null`) or any spacing
gives `None`;
  • a string value ends at the first ", so escaped quotes are not
supported and escapes are not decoded;
  • the payload is everything after "payload": up to the end of the input,
minus one trailing `}`, so `payload` must be the last field.

Examples

march> Channel.parse("{\"join_ref\":\"0\",\"ref\":\"1\",\"topic\":\"room:lobby\",\"event\":\"new_msg\",\"payload\":{\"body\":\"hi\"}}")
Some(ChannelMsg("0", "1", "room:lobby", "new_msg", "{\"body\":\"hi\"}"))
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.parse(Channel.serialize(cm)) == Some(cm)
true
march> Channel.parse("{\"join_ref\": \"0\"}")
None
fnpushpush(sock, event, payload)#

Push a message directly to this client only: casts ChannelPush(event, payload) to the socket's transport pid. Fire and forget; returns whatever actor_cast returns. Requires actor runtime.

Channel.push(sock, "new_msg", "{\"body\":\"hi\"}")
fnreplyreply(status, payload, st)#

Reply(status, payload, st): reply to the client's ref with status (conventionally "ok" or "error") and the JSON payload, and continue with state st.

Examples

march> Channel.reply("ok", "{}", 5)
Reply("ok", "{}", 5)
fnreply_msgreply_msg(join_ref, ref, topic, status, response_payload)#

A phx_reply message answering the client's ref on topic, with payload {"status":<status>,"response":<response_payload>} (response_payload embedded verbatim as JSON).

Examples

march> Channel.msg_payload(Channel.reply_msg("0", "1", "room:lobby", "ok", "{}"))
"{\"status\":\"ok\",\"response\":{}}"
fnroute_handlerroute_handler(r)#

The handler module name of a route.

Examples

march> Channel.route_handler(Channel.channel_route("room:*", "RoomChannel"))
"RoomChannel"
fnroute_patternroute_pattern(r)#

The topic pattern of a route.

Examples

march> Channel.route_pattern(Channel.channel_route("notify:user", "NotifyChannel"))
"notify:user"
fnserializeserialize(m)#

Serialize a ChannelMsg to a JSON string conforming to the Phoenix channel wire format: {"join_ref":"0","ref":"1","topic":"room:lobby","event":"new_msg","payload":{}} The four string fields are escaped for " and backslash only (control characters are not escaped); the payload is embedded verbatim and must already be valid JSON.

Examples

march> Channel.serialize(Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}"))
"{\"join_ref\":\"0\",\"ref\":\"1\",\"topic\":\"room:lobby\",\"event\":\"new_msg\",\"payload\":{}}"
fnsocket_assignssocket_assigns(sock)#

The socket's assigns as (key, value) pairs, most recently assigned first. A key assigned twice appears twice (the newer pair first).

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_assigns(sock)
[]
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_assigns(Channel.assign(Channel.assign(sock, "user", "alice"), "role", "admin"))
[("role", "admin"), ("user", "alice")]
fnsocket_channel_pidsocket_channel_pid(sock)#

The pid of the channel process (green thread) serving this join.

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_channel_pid(sock)
10
fnsocket_join_refsocket_join_ref(sock)#

The ref of the client's original join message, which replies on this channel echo back as join_ref.

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_join_ref(sock)
"0"
fnsocket_topicsocket_topic(sock)#

The topic the socket joined, e.g. "room:lobby".

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_topic(sock)
"room:lobby"
fnsocket_transport_pidsocket_transport_pid(sock)#

The pid of the transport (WebSocket frame loop) the socket belongs to.

Examples

march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_transport_pid(sock)
11
fnstopstop(reason, st)#

Stop(reason, st): close this channel with reason; st is the final state handed to the leave callback.

Examples

march> Channel.stop("kicked", 5)
Stop("kicked", 5)