phollyer / elm-phoenix-websocket / Phoenix.Socket

This module can be used to talk directly to PhoenixJS without needing to add anything to your Model. You can send and receive messages to and from the JS Socket from anywhere in your Elm program. That is all it does and all it is intended to do.

If you want more functionality, the top level Phoenix module takes care of a lot of the low level stuff such as automatically connecting to the Socket.

Connecting


type ConnectOption
    = BinaryType String
    | HeartbeatIntervalMillis Basics.Int
    | Logger Basics.Bool
    | LongpollerTimeout Basics.Int
    | ReconnectAfterMillis Basics.Int
    | ReconnectSteppedBackoff (List Basics.Int)
    | RejoinAfterMillis Basics.Int
    | RejoinSteppedBackoff (List Basics.Int)
    | Timeout Basics.Int
    | Transport String
    | Vsn String

The options that can be set on the Socket when instantiating a new Socket(url, options) on the JS side.

See https://hexdocs.pm/phoenix/js/#Socket for more info on the options and the effect they have.

However, there are two potential instances where we have to work around the inability to send functions through ports. This is if you wish to employ a backoff strategy that increases the time interval between repeated attempts to reconnect or rejoin.

To do this on the JS side, you would provide a function that returns an Int. But because we can't send functions through ports, the way to create these functions is to also use the ...SteppedBackoff counterparts:

[ ReconnectAfterMillis 1000
, ReconnectSteppedBackoff [ 10, 20, 50, 100, 500 ]
, RejoinAfterMillis 10000
, RejoinSteppedBackoff [ 1000, 2000, 5000 ]
]

On the JS side, the above options result in:

{ reconnectAfterMs: function(tries){ return [10, 20, 50, 100, 500][tries - 1] || 1000 },
  rejoinAfterMs: function(tries){ return [1000, 2000, 5000][tries - 1] || 10000 }
}

For a consistent time interval simply ignore the ...SteppedBackoff options:

[ ReconnectAfterMillis 1000
, RejoinAfterMillis 10000
]


type alias Params =
Json.Encode.Value

A type alias repesenting the params to be sent when connecting, such as authentication params like username and password.


type alias PortOut msg =
{ msg : String
, payload : Json.Encode.Value } -> Platform.Cmd.Cmd ms
}

A type alias representing the port function required to send messages out to the accompanying JS.

You will find this port function in the Port module.

connect : List ConnectOption -> Maybe Params -> PortOut msg -> Platform.Cmd.Cmd msg

Connect to the Socket, providing any required ConnectOptions and Params as well as the port function to use.

import Json.Encode as JE
import Phoenix.Socket as Socket
import Ports.Phoenix as Port

-- A simple connection

Socket.connect [] Nothing Port.phoenixSend

-- A connection with options and auth params

options =
    [ Socket.HeartbeatIntervalMillis 500
    , Socket.Timeout 10000
    ]

params =
    JE.object
        [ ("username", JE.string "Joe Bloggs")
        , ("password", JE.string "password")
        ]

Socket.connect options (Just params) Port.phoenixSend

Disconnecting

disconnect : Maybe Basics.Int -> PortOut msg -> Platform.Cmd.Cmd msg

Disconnect the Socket, maybe providing a status code for the closure.

Receiving Messages


type alias ClosedInfo =
{ reason : Maybe String
, code : Basics.Int
, wasClean : Basics.Bool
, type_ : String
, isTrusted : Basics.Bool 
}

A type alias representing the information received when the Socket closes.


type alias Topic =
String

A type alias representing the Channel topic id. For example "topic:subTopic".


type alias Event =
String

A type alias representing an event received from a Channel.


type alias Payload =
Json.Encode.Value

A type alias representing data that is received from a Channel.


type alias ChannelMessage =
{ topic : Topic
, event : Event
, payload : Payload
, joinRef : Maybe String
, ref : Maybe String 
}

A type alias representing a raw Channel message received by the Socket.


type alias PresenceMessage =
{ topic : Topic
, event : Event
, payload : Payload 
}

A type alias representing a raw Presence message received by the Socket.


type alias HeartbeatMessage =
{ topic : Topic
, event : Event
, payload : Payload
, ref : String 
}

A type alias representing a raw Heartbeat received by the Socket.


type alias AllInfo =
{ connectionState : String
, endPointURL : String
, isConnected : Basics.Bool
, makeRef : String
, protocol : String 
}

A type alias representing all of the info available about the Socket.


type Info
    = All AllInfo
    | ConnectionState String
    | EndPointURL String
    | IsConnected Basics.Bool
    | MakeRef String
    | Protocol String

Information received about the Socket.


type InternalError
    = DecoderError String
    | InvalidMessage String

An InternalError should never happen, but if it does, it is because the JS is out of sync with this package.

If you ever receive this message, please raise an issue.


type Msg
    = Opened
    | Closed ClosedInfo
    | Connecting
    | Disconnecting
    | Error String
    | Channel ChannelMessage
    | Presence PresenceMessage
    | Heartbeat HeartbeatMessage
    | Info Info
    | InternalError InternalError

All of the messages you can receive from the Socket.


type alias PortIn msg =
({ msg : String
, payload : Json.Encode.Value } -> msg) -> Platform.Sub.Sub ms
)

A type alias representing the port function required to receive a Msg from the Socket.

You will find this port function in the Port module.

subscriptions : (Msg -> msg) -> PortIn msg -> Platform.Sub.Sub msg

Subscribe to receive incoming Socket messages.

import Phoenix.Socket as Socket
import Ports.Phoenix as Port

type Msg
  = SocketMsg Socket.Msg
  | ...


subscriptions : Model -> Sub Msg
subscriptions _ =
    Socket.subscriptions
        SocketMsg
        Port.socketReceiver

Socket Information

Request information about the Socket.

connectionState : PortOut msg -> Platform.Cmd.Cmd msg

endPointURL : PortOut msg -> Platform.Cmd.Cmd msg

info : PortOut msg -> Platform.Cmd.Cmd msg

isConnected : PortOut msg -> Platform.Cmd.Cmd msg

makeRef : PortOut msg -> Platform.Cmd.Cmd msg

protocol : PortOut msg -> Platform.Cmd.Cmd msg

Logging

Here you can log data to the console, and activate and deactive the socket's logger, but be warned, there is no safeguard when you compile such as you get when you use Debug.log. Be sure to deactive the logging before you deploy to production.

However, the ability to easily toggle logging on and off leads to a possible use case where, in a deployed production environment, an admin is able to see all the logging, while regular users do not.

log : String -> String -> Json.Encode.Value -> PortOut msg -> Platform.Cmd.Cmd msg

Log some data to the console.

import Json.Encode as JE
import Ports.Phoenix as Port

log "info" "foo"
    (JE.object
        [ ( "bar", JE.string "foo bar" ) ]
    )
    port.phoenixSend

-- info: foo {bar: "foo bar"}

In order to receive any output in the console, you first need to activate the Socket's logger. There are two ways to do this. You can use the startLogging function, or you can pass the Logger True ConnectOption to the connect function.

import Ports.Phoenix as Port

connect [ Logger True ] Nothing Port.phoenixSend

startLogging : PortOut msg -> Platform.Cmd.Cmd msg

Activate the Socket's logger function. This will log all messages that the Socket sends and receives.

stopLogging : PortOut msg -> Platform.Cmd.Cmd msg

Deactivate the Socket's logger function.