mbr/elm-wss/1.0.0

NOTE: the latest version is 3.0.0

WebsocketSimple

A small, typed WebSocket client for Elm with a compact, auditable JavaScript runtime. It supports multiple connections, lifecycle events, and text or JSON messages.

Installation

Install the Elm package:

elm install mbr/elm-wss

Elm packages cannot declare ports. Make Main.elm a port module, or use another application-owned port module, then add these declarations:

import WebsocketSimple exposing (CommandPort, EventPort)


port wsCmd : CommandPort msg


port wsMsg : EventPort msg

The JavaScript runtime expects the exact port names wsCmd and wsMsg.

Copy js/elm-websockets.js into the browser assets. Load the runtime and compiled Elm application, initialize Elm, and then initialize the runtime:

<script src="elm-websockets.js"></script>
<script src="app.js"></script>
<script>
var app = Elm.Main.init({
  node: document.getElementById("elm")
});
ElmWebsockets.initApp(app);
</script>

The JavaScript initialization is a no-op when the application does not contain the command port, so it can remain in the bootstrap while WebSocket code is temporarily unused.

Ports

Pass the application-owned ports described by these aliases to commands and subscriptions.

type alias CommandPort msg =
( String, String, Value ) -> Cmd msg

Type of an application-owned port that sends encoded commands to the JavaScript runtime.

type alias EventPort msg =
(( String, String, Value ) -> msg) -> Sub msg

Type of an application-owned port that receives encoded events from the JavaScript runtime.

Basic usage

Subscribe to events before opening a connection, and wait for Connected before transmitting:

import WebsocketPorts as Ports
import WebsocketSimple as WebSocket


type Msg
    = WebSocketEvent WebSocket.RawMsg


init : () -> ( List String, Cmd Msg )
init _ =
    ( [], WebSocket.open Ports.wsCmd "ws://127.0.0.1:8765" )


update : Msg -> List String -> ( List String, Cmd Msg )
update msg model =
    case msg of
        WebSocketEvent (WebSocket.Connected _) ->
            ( model, WebSocket.send Ports.wsCmd (WebSocket.Transmit "hello") )


        WebSocketEvent (WebSocket.Text value) ->
            ( value :: model, WebSocket.close Ports.wsCmd )


        WebSocketEvent (WebSocket.Disconnected _) ->
            ( model, Cmd.none )


        WebSocketEvent (WebSocket.TransportError error) ->
            ( WebSocket.errorToString error :: model, Cmd.none )


subscriptions : List String -> Sub Msg
subscriptions _ =
    Sub.map WebSocketEvent (WebSocket.subscribe Ports.wsMsg)

Commands

send and its convenience functions use an implicit "default" handle. Use sendWithHandle for multiple connections.

type Cmd
= Open Url (List String)
| Transmit String
| Close (Maybe CloseRequest)

Command sent to a WebSocket connection.

  • Open opens the URL with an ordered list of requested subprotocols. An empty list requests none.
  • Transmit sends a text frame. A rejected transmission produces a TransportError; an accepted transmission produces no acknowledgement.
  • Close Nothing preserves the browser's default close behavior. Close (Just request) sends the supplied code and reason unchanged, leaving validation to the browser.
type alias CloseRequest =
{ code : Int
, reason : String
}

Describes an application-requested close.

Use an empty reason to send only the explicit close code.

open : CommandPort msg -> String -> Cmd msg

Open the implicit default connection without requesting a subprotocol.

close : CommandPort msg -> Cmd msg

Close the implicit default connection with the browser's default status.

send : CommandPort msg -> Cmd -> Cmd msg

Send a command to the implicit "default" connection.

sendWithHandle : CommandPort msg -> Handle -> Cmd -> Cmd msg

Send a command to the connection identified by a handle.

Handles

Each connection is identified by an opaque handle encoded as a string at the port boundary.

chat : WebSocket.Handle
chat =
    WebSocket.handle "chat"


alerts : WebSocket.Handle
alerts =
    WebSocket.handle "alerts"


openConnections : Cmd msg
openConnections =
    Cmd.batch
        [ WebSocket.sendWithHandle Ports.wsCmd chat (WebSocket.Open chatUrl [])
        , WebSocket.sendWithHandle Ports.wsCmd alerts (WebSocket.Open alertsUrl [])
        ]
type Handle

Identifies a particular WebSocket connection.

handle : String -> Handle

Construct a WebSocket handle from its port-boundary representation.

Return a WebSocket handle's string representation.

Events

subscribe discards handle information. Use subscribeWithHandle to distinguish events from multiple connections.

type Msg
    = WebSocketEvent ( WebSocket.Handle, WebSocket.RawMsg )


subscriptions : model -> Sub Msg
subscriptions _ =
    Sub.map WebSocketEvent (WebSocket.subscribeWithHandle Ports.wsMsg)
type RawMsg
= Connected (Maybe String)
| Disconnected CloseDetails
| Text String
| TransportError TransportErrorDetails

Raw event received from a WebSocket connection.

  • Connected means the connection can transmit and contains the negotiated subprotocol, if any.
  • Disconnected contains the browser's close details and may occur without a preceding Connected event.
  • Text contains a received text frame. Binary frames are unsupported and produce a transport error.
  • TransportError describes a construction, send, close, browser, unsupported-data, or port-decoding failure.
type alias CloseDetails =
{ code : Int
, reason : String
, wasClean : Bool
, initiatedLocally : Bool
}

Details supplied when a WebSocket closes.

The event may arrive even when the connection never opened. initiatedLocally records whether this client successfully requested closure.

= ConstructionFailure
| SendFailure
| CloseFailure
| BrowserFailure
| SendRejection
| UnsupportedData
| PortDecodingFailure

Categorizes a WebSocket transport error.

, message : String
}

Describes a categorized WebSocket transport error.

subscribe : EventPort RawMsg -> Sub RawMsg

Subscribe to raw events from the implicit default connection.

This discards the originating handle from every event.

subscribeWithHandle : EventPort ( Handle, RawMsg ) -> Sub ( Handle, RawMsg )

Subscribe to raw events paired with their originating handles.

Render a transport error category as a string.

errorToString : TransportErrorDetails -> String

Render a transport error as a readable string containing its category and message.

JSON

Regular subscriptions expose incoming text frames as Text String. Typed subscriptions decode text frames while keeping transport errors separate from payload decoding failures.

import Json.Decode as Decode


type alias Payload =
    { message : String }


payloadDecoder : Decode.Decoder Payload
payloadDecoder =
    Decode.map Payload (Decode.field "message" Decode.string)


type Msg
    = WebSocketMessage (WebSocket.Msg Payload)


subscriptions : model -> Sub Msg
subscriptions _ =
    WebSocket.subscribeMsg Ports.wsMsg payloadDecoder WebSocketMessage
type Msg t
= Established (Maybe String)
| Closed CloseDetails
| Received t
| TransportFailure TransportErrorDetails
| PayloadDecodeFailure String Error

Typed event received from a WebSocket connection carrying JSON text frames.

Transport failures remain separate from payload decoding failures. PayloadDecodeFailure retains both the original text and the Json.Decode.Error.

subscribeMsg : EventPort msg -> Decoder t -> (Msg t -> msg) -> Sub msg

Subscribe to typed JSON events while discarding handle information.

subscribeMsgWithHandle : EventPort msg -> Decoder t -> (( Handle, Msg t ) -> msg) -> Sub msg

Subscribe to typed JSON events paired with their originating handles.

transmitMsg : CommandPort msg -> (t -> Value) -> t -> Cmd msg

Encode a value as JSON and transmit it through the implicit default connection.

transmitMsgWithHandle : CommandPort msg -> Handle -> (t -> Value) -> t -> Cmd msg

Encode a value as JSON and transmit it through the connection identified by a handle.

parseIncoming : Decoder t -> RawMsg -> Msg t

Convert a raw event into a typed JSON event.