What it does: Type-safe WebSocket management via ports, with two features I couldn’t find together elsewhere:
Binary (Bytes) support with zero JSON overhead. An XHR monkeypatch routes ArrayBuffer data directly between Elm’s Http kernel and the WebSocket — no JSON encoding/decoding of binary payloads. A 100MB round trip through an echo server takes about 1 second. Credit to @lue-bird for the technique. (EDIT: for showing me the technique ^^)
Configurable reconnection with exponential backoff, jitter, max retries, and skip codes — all managed on the JS side, with Reconnecting, Reconnected, and ReconnectFailed events delivered to Elm.
Setup is minimal — declare two ports and call one JS init function:
port wsOut : WS.CommandPort msg
port wsIn : WS.EventPort msg
import * as wsm from "elm-websocket-manager";
wsm.init({ wsOut: app.ports.wsOut, wsIn: app.ports.wsIn });
API uses bind to produce a record of command functions:
chatConfig = WS.init "ws://example.com/chat"
chatWs = WS.bind chatConfig wsOut GotChatEvent
-- Then use it directly
chatWs.open
chatWs.sendText "hello"
chatWs.sendBytes myBytes
chatWs.close
Events are a single sum type — pattern match to handle everything:
case event of
WS.Opened -> ...
WS.MessageReceived data -> ...
WS.BinaryReceived bytes -> ...
WS.Closed info -> ...
WS.Reconnecting info -> ...
WS.Reconnected -> ...
WS.ReconnectFailed -> ...
WS.Error message -> ...
WS.NoOp -> ( model, Cmd.none )
It also supports multiple simultaneous connections, RFC 6455 close codes as a union type, and a connection state helper for UI updates.
correction: I played no part in discovering the XHR patching trick. Full credit to Andreas Molitor (anmolitor (Andreas Molitor) · GitHub) or whoever discovered it first
Great stuff and I particularly appreciate the automatic reconnect, and passing up of connection fail and other error events.
I would like to express my frustration at the lack of support for Bytes over ports. It is such an obviously useful thing to be able to do, yet we have to resort to this XHR hack to achieve it. Unfortunately it seems like this hack is going from being a dirty secret of the application writer to a mainstream technique of the re-usable package author - which means it will begin to propagate into more and more places.
If we had bytes over ports, we would still have an Elm kernel that captures this use case, and therefore keeps Elm code portable should that kernel be re-implemented for some other platform. Sadly, this is not a sustainable way to extend Elms capabilities.
No criticism of this websocket package intended - what else could you have done to support bytes in a reasonable way (not base64 encoding!).
For reference here is the PR for a patch against guida to support bytes over ports, ported from the Lamdera implementation. It isn’t huge or difficult:
Yes having support for Bytes over ports would be awesome. Lamdera has it as you mentioned.
I’m curious what are the reasons for not having it in the first place. I can imagine it breaking some immutability guarantees if the bytes are mutated once they land in JS. But I mean, that is also already the case for Value today sent through a JS → Elm port. Bytes are already not comparable anyway in Elm, so I think the tradeoff of supporting Bytes through ports is worth it IMO.