Module nh_channel

The Phoenix channel protocol NervesHub speaks, as a pure state machine.

Description

The Phoenix channel protocol NervesHub speaks, as a pure state machine.

No processes, no timers, no socket. Every function takes a state and returns a new state plus a list of actions for the caller to perform:

  {send, Binary}   %% write this to the socket
  {event, Term}    %% tell the application this happened

Keeping it pure is what lets the protocol be tested against a real NervesHub from a desktop, over any WebSocket client, before any of it runs on a device.

The wire format

Phoenix's v2 serializer is a five element array rather than an object:

  [JoinRef, Ref, Topic, Event, Payload]

Two details are easy to get wrong and fail quietly:

Rejoining

A channel does not survive a socket reconnect. connected/1 must be called on every connection, including reconnections, and it starts a fresh join with a new join reference. A client that reconnects the socket without rejoining looks healthy and silently stops receiving updates.

Data Types

action()

action() = {send, binary()} | {event, term()}

state()

abstract datatype: state()

Function Index

add_topic/3Register another topic to join on this socket.
connected/1Called on every connection, including reconnections.
disconnected/1The socket went away.
handle_text/2Handle a text frame from the socket.
heartbeat/1A heartbeat frame.
joined/1Whether the join has been acknowledged.
joined/2Whether a named topic's join has been acknowledged.
new/1A channel that has not connected yet.
push/3Push an event to the device channel.
push/4Push an event to a named topic.
topics/1Every topic registered, in join order.

Function Details

add_topic/3

add_topic(Topic::binary(), Params::map(), State::state()) -> state()

Register another topic to join on this socket.

One socket carries several channels — the device topic, and console when the application wants one. Each is joined separately and has its own join reference, but they share the socket's reference counter: Phoenix correlates a reply by reference, and two channels numbering from 1 apiece would produce two different frames with the same reference.

Topics keep the order they were added, so the device topic is joined first and the rest follow.

connected/1

connected(State0::state()) -> {state(), [action()]}

Called on every connection, including reconnections.

disconnected/1

disconnected(State::state()) -> state()

The socket went away.

Only the join is lost. The parameters and the reference counter are kept: a fresh channel would restart references at 1, and a late reply from the old connection could then be mistaken for a reply to the new join.

handle_text/2

handle_text(Text::binary(), State::state()) -> {state(), [action()]}

Handle a text frame from the socket.

heartbeat/1

heartbeat(State0::state()) -> {state(), [action()]}

A heartbeat frame. NervesHub closes a socket that stops sending these.

joined/1

joined(State::state()) -> boolean()

Whether the join has been acknowledged.

joined/2

joined(Topic::binary(), State::state()) -> boolean()

Whether a named topic's join has been acknowledged.

new/1

new(Params::map()) -> state()

A channel that has not connected yet.

Params is the join payload: what the device reports about itself.

push/3

push(Event::binary(), Payload::map(), State::state()) -> {state(), [action()]}

Push an event to the device channel.

push/4

push(Topic::binary(), Event::binary(), Payload::map(), State0::state()) -> {state(), [action()]}

Push an event to a named topic.

topics/1

topics(X1::state()) -> [binary()]

Every topic registered, in join order.


Generated by EDoc