A managed WebSocket connection.
The connection manager keeps a WebSocket connected and reconnects when it closes or reports a connection failure. By default, it retries immediately, then backs off exponentially from 250 milliseconds to a maximum delay of one minute. Retries are unlimited, and establishing a connection resets the backoff.
Each attempt has its own handle by default; see Delayed replies below. The manager handles connection lifecycle and retries so your application can focus on its protocol.
Using the application-owned ports described in WebsocketSimple, this example
replies to server-sent text with its reverse and counts palindromic inputs:
import WebsocketPorts as Ports
import WebsocketSimple.Connection as Connection
type alias Model =
{ connection : Connection.Model
, palindromes : Int
}
type Msg
= ConnectionMsg Connection.Msg
| OtherMessage
init : () -> ( Model, Cmd Msg )
init _ =
let
( connection, action ) =
Connection.init (Connection.config "wss://example.com/messages")
in
( { connection = connection, palindromes = 0 }
, Connection.perform Ports.wsCmd ConnectionMsg action
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ConnectionMsg connectionMsg ->
let
( connection, action, incoming ) =
Connection.update connectionMsg model.connection
updated =
{ model | connection = connection }
( next, command ) =
case incoming of
Just event ->
handleEvent event updated
Nothing ->
( updated, Cmd.none )
in
( next
, Cmd.batch [ Connection.perform Ports.wsCmd ConnectionMsg action, command ]
)
OtherMessage ->
( model, Cmd.none )
handleEvent : Connection.Event -> Model -> ( Model, Cmd Msg )
handleEvent event model =
case event of
Connection.Text body ->
let
reversed =
String.reverse body
next =
if body == reversed then
{ model | palindromes = model.palindromes + 1 }
else
model
in
( next
, Connection.send Ports.wsCmd reversed model.connection
)
_ ->
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions _ =
Connection.subscribe Ports.wsMsg |> Sub.map ConnectionMsgReceiving "ABC" sends back "CBA". Receiving "ABA" sends back "ABA" and
increments palindromes.
By default, the manager generates a fresh handle for each connection attempt. A
reply command created while handling a received message in update stays bound to
that connection: reconnecting cannot redirect it to a replacement socket. This is
not a delivery guarantee; the socket may close before the command executes.
If work continues asynchronously after update returns, and its reply must not
cross connections, use one of these approaches:
get when starting the work and compare it with get
when the result arrives. Discard the result if the handles no longer match.Established by resetting connection-specific protocol state and
invalidating queued work or outstanding request IDs from the previous connection.These approaches are useful when your protocol has connection-specific state, such as an initial snapshot followed by updates. The manager does not reset application state itself; your application decides what to retain across reconnects.
If replies need not stay on their original connection, simply send using the current
model. withHandleReuse True additionally lets already-created commands address a
replacement socket by reusing the same handle across attempts. This only works if
the replacement is open when the command executes. Handle comparisons can no longer
distinguish attempts, though Established is still emitted for each successful
connection. Calls to send and sendBinary while disconnected are still discarded;
there is no buffering or replay.
Call fail when your application determines that the current connection is unusable:
Connection.fail "Update sequence gap" model.connection. This records an
ApplicationError, closes the socket, and follows the configured retry schedule
and attempt limit. It only affects an established connection; calls while connecting,
retrying, waiting, or exhausted do nothing. Transport failures during connection
attempts still trigger the normal retry behavior.
Use reconnect for a "Reconnect now" button. It replaces an active connection or
skips a pending backoff, starting a fresh attempt immediately. It also works after
exhaustion, resetting the retry count and backoff while retaining your configuration.
Old retry callbacks cannot restart the connection again. Like automatic retries,
it honors withHandleReuse and leaves application protocol state to you.
Both functions return a model and an action to pass to perform:
reconnectNow : Model -> ( Model, Cmd Msg )
reconnectNow model =
let
( connection, action ) =
Connection.reconnect model.connection
in
( { model | connection = connection }
, Connection.perform Ports.wsCmd ConnectionMsg action
)A server can check client liveness by sending WebSocket protocol pings and waiting for pongs. Browsers reply automatically. JavaScript cannot send or observe these control frames, so they do not count as activity for client-side checks.
A client can check server liveness by timing out a silent server. The server
sends unsolicited heartbeat data frames, either periodically or only when otherwise
idle. An empty binary frame works well: the application can discard Binary events
whose payload has Bytes.width zero. The JSON adapter passes binary frames through
too, so these heartbeats need no JSON representation.
Enable the client-side timeout with Connection.withServerTimeout 45000. It starts
when the connection opens; every incoming text or binary frame counts as activity.
If the server stays silent for the configured milliseconds, the manager records
ServerTimeout, closes the socket, and follows its retry policy. Checks are disabled
by default and do not time out connection attempts. Choose a timeout longer than
the heartbeat interval, with room for network and scheduling delays. Watchdog
callbacks use their scheduled time; delayed processing can postpone expiry.
Configure an indefinitely maintained connection with capped exponential backoff.
Choose the delay between retries.
Limit attempts in a retry sequence, including its initial attempt.
Give the manager its own handle namespace. Use distinct namespaces when maintaining multiple connections; socket handles are generated within it.
Reuse one handle across attempts when enabled. The default is False.
Delayed sends can then address a replacement socket, and handles no longer
identify individual attempts. Retry messages still belong to distinct attempts.
Request an ordered list of WebSocket subprotocols.
Reconnect after this many milliseconds without incoming data on an established connection. Disabled by default. Nonpositive or non-finite values disable checks.
A retrying connection whose successful value is its connected socket handle.
Socket input or a scheduled wakeup. Route these messages back to update.
Application events from the managed connection.
Established reports a successful connection and its negotiated subprotocol, if any.
Text carries an incoming text frame.
Binary carries an incoming binary frame, including empty heartbeat frames.
Error reports a transport error that does not trigger reconnection. Failures
that trigger reconnection are available through state instead.
Connection effects interpreted by perform.
None performs no work.
Open opens a socket with the supplied handle, URL, and requested subprotocols.
Close closes a socket and optionally delivers a retry message after a delay in milliseconds.
Replace closes the old socket and opens its replacement without a retry delay.
CheckAfter delivers a watchdog message after a delay in milliseconds. The message
carries its scheduled firing time; late callbacks can postpone expiry, not advance it.
The reason a connection was lost or an attempt failed.
Process a socket event or retry, returning an application event when appropriate. Events with unrelated handles and obsolete retry messages are ignored.
Socket closure, construction failure, and browser transport failure schedule
reconnection without emitting an application event. Other errors are reported as
Error without reconnecting.
Fail an established connection, recording an ApplicationError and closing
the socket. Retries follow the configured schedule and attempt limit. Calls in
any other state do nothing; use reconnect to replace a pending attempt.
Start a fresh attempt immediately, replacing any active connection or skipping its backoff. Resets the retry count, even after exhaustion, and invalidates pending retry callbacks. Configuration, including handle reuse, is preserved.
Inspect connection progress and the latest failure through Again.Remote.
Subscribe to socket events through an application-owned event port.
Execute one connection action through your application. A reused handle is replaced with a single open command, which closes the old socket in the runtime. Fractional watchdog delays round up to avoid waking before their logical time.
Send text through the connected socket. Disconnected sends are discarded, not buffered or replayed. Delivery is best-effort.
Send binary data with the same best-effort behavior as send.
Connection settings, applied before the first attempt.