EnTrance clients talk to a server over a single websocket. A "channel" is like a tunnel inside that websocket, dedicated to one part of your Elm application.
Simple apps need only a single channel. The value comes when your app becomes more complex, and gains more modular structure. Often you end up with several different "sub-apps", each of which has responsibility for some part of the overall user experience. In such cases, it's easiest to create one channel per sub-app, so that each sub-app has its own two-way communciation mechanism with the features it invokes on the server, without coupling it to the other sub-apps.
You should never need more than one channel in a given module. If you're thinking about that, you probably want multiple RpcData values in your model instead.
Looking at some of the sample apps, eg the notes app may be a helpful supplement to the description here.
A channel is created via either two or three ports in an Elm module. The two-port case looks like this:
import EnTrance.Channel as Channel
port myAppSend : Channel.SendPort msg
port myAppRecv : Channel.RecvPort msgThis creates a send/receive port pair, implementing one channel, called "myApp". You can choose almost anything you like for the "myApp" bit.
This is a bit weird and magical, based on the names of the ports:
the "Send" suffix tells EnTrance that myAppSend is used for sending messages
the "myApp" prefix is used to add a "channel": "my_app" field to any
messages send to the server via that port
the server reflects this "channel": "my_app" back in any replies
these get routed back to the myAppRecv port based on the "myApp" prefix
and "Recv" suffix
So you can choose any channel name you like ("myApp" in this case), but:
you have to apply that name consistently across all the ports making up the channel ("myAppSend" and "myAppRecv" in this case)
if you have more than one channel in your app, they must have unique names
you can't use "error" or "inject" for the channel name, since these are taken (see below)
The three-port case is where you also specify a third port:
port myAppIsUp : Channel.IsUpPort msgThis gives you up/down notifications for the channel state, ie whether there is
a server on the end of it. If you have a Msg constructor like this:
type Msg = ...
| AppIsUp Booland instantiate an IsUpPort:
port myAppIsUp : Channel.IsUpPort msgthen you can just subscribe to myAppIsUp AppIsUp and you'll receive an
AppIsUp message whenever the connection state changes.
A port to receive notifications over a channel from the server.
A port from which to be notified of channel up/down status.
You typically send an RPC message to the server using sendSimpleRpc, sendRpc or send. The key distinction between these three options is:
sendSimpleRpc is the easiest way to send an RPC
sendRpc allows multiple simultanous independent RPCs, but it's up to you to
set the right RpcData to Loading.
send is for async (fire-and-forget) outbound messages.
Simplified variant of sendRpc that assumes the relevant
RpcData in your model is called result, and sets the state to
Loading for you.
This assumes your model includes a field called sendPort of type SendPort
msg.
Send an RPC request. If you're just starting out, sendSimpleRpc may be easier. If you need more control, use sendRpcCmd.
Updating the relevant RpcData to state Loading is your
responsibility.
This assumes your model includes a field called sendPort of type SendPort
msg.
Simple way to send an async message. If you need more control, use sendCmd.
This assumes your model includes a field called sendPort of type SendPort
msg.
If you want more low-level control, you can create just a raw Cmd for either
RPC or async requests using the following functions.
Create a command to send a Request over a channel with RPC semantics (the
mainline case). In many cases, using sendRpc is nicer than calling
this directly.
RPC semantics mean that exactly one reply notification is expected (either success or error), and that a unique message identifier is used to ensure that out-of-order replies don't get mistaken for the reply we're waiting for here.
port appSend : SendPort cmd
Request.new "some_message"
|> Channel.sendRpcCmd appSendHandy way to send a list of requests in one shot.
Create a command to send a Request over a channel with async
(fire-and-forget) semantics.
port appSend : SendPort msg
Request.new "some_message"
|> Channel.sendCmd myAppSendHelper to send a list of async commands in one shot.
In order to receive notifications from the server (such as RPC replies), use
sub to subscribe to notifications you receive on this channel. The
notification arrives in JSON format, so you need to supply a set of candidate
JSON decoders, that turn any expected JSON notification into a Msg for your
update function.
For example, suppose you are using the built-in Syslog and Netconf features, so
expect to receive one of those two notifications on the "myApp" channel. Just
create Msg constructors for those two options:
type Msg
= ...
| GotSyslog Syslog
| GotNetconfResult String
| Error StringThen the 'sub' function turns the type-safe decoder for each feature (Syslog.decode and Netconf.decodeRequest) into a subscription like this:
Channel.sub myAppRecv
Error
[ Syslog.decode GotSyslog
, Netconf.decodeResult GotNetconfResult
]Such a subscription means that whenever you receive an incoming notification on the channel, either:
one of your decoders will succeed, and you'll receive a GotSyslog or
GotNetconfResult message
you'll receive an Error message with a string explaining what went wrong in
the decoding process
Subscribe to a receive port, and use the specified list of decoders to turn
JSON notifications for that channel into Msgs of your choice. Takes a receive
port, an error message constructor, and a list of individual notification
decoders.
There are two "magic" channels, that can be instantiated only once in your application. These consume the channel names "error" and "inject".
You should instantiate exactly one "error" receive port in your application, and handle the (rare) errors signalled there:
port errorRecv : Channel.ErrorRecvPortThis is used to signal unexpected errors that can't be associated with a particular operation, such as the server complaining it can't decode valid JSON out of a request it received.
A port from which to be notified of global errors of type String. You
should have exactly one of these in your application, named errorPort.
Finally, you can optionally create a pair of send/receive channels with the
magic name inject, and special types:
port injectSend : Channel.InjectSendPort
port injectRecv : Channel.InjectRecvPortThese "loop round", so a JSON message send out on injectSend is received on
injectRecv. This can provide a handy way for complex apps to feed messages
from sub-apps back to the top level in a clean way. (You can do it lots of
other ways if you prefer.) For an example of how to use it, see the example
code.
A port to inject a JSON value out of. You can have one of these in your
application if you want, called injectSend.
A port to subcribe to, for injected JSON values. You can have one of these
in your application if you want to, named injectRecv.
A port to send messages over a channel to the server.