glyde/websocket

A WebSocket client.

let assert Ok(bye) = websocket.close_code(1000)

let socket = websocket.open("wss://gateway.discord.gg/?v=10")
let socket = websocket.send_text(socket, identify)
let socket = websocket.close(socket, bye, "bye")

Every call hands back the socket to use next, the writes included: a write that fails is what ends a socket most of the time, and the reason for it comes out of the next poll.

Mask bytes and the handshake nonce come from crypto:strong_rand_bytes, never from glyde/rng, which is reproducible and so is the very thing masking exists to stop.

No reconnect: glyde/gateway owns redialling. No ping, though a peer’s is answered here and never reported. No extensions, no ws://, no server role, no subprotocols, no request headers, no backpressure.

The events are glyde/transport.Event, not ours: they are the vocabulary every transport speaks, and one of those is this client.

Types

A close code a client is allowed to put on the wire, and the only thing close accepts: a code it would have turned down cannot reach it.

pub opaque type CloseCode

A socket and what it still owes the caller. It carries bytes read but not yet handed over, and every call that changes it hands back a new one, so always use the newest.

Nothing can be reported before open returns, so the events it cannot deliver wait in here until the first poll.

pub opaque type Socket

Values

pub fn close(
  socket: Socket,
  code: CloseCode,
  reason: String,
) -> Socket

Start closing: the frame goes out and the transport comes down, so carry the socket this hands back. It refuses every write and owes exactly one Closed(1006, ""), which a later poll reports.

The reason is cut to 123 bytes. Closing a socket that is already ending or finished leaves it alone.

pub fn close_code(number: Int) -> Result(CloseCode, Int)

1000, or anything from 3000 to 4999, which is what RFC 6455 lets a client send. Discord uses 1000 to end a session and 4000 to keep it resumable. The Error hands the number back.

pub const dial_timeout: Int

Milliseconds. Our number: Discord’s gateway answers in well under a second.

pub fn drop(socket: Socket) -> Socket

Abandon the socket, with no close frame. For a peer that has already gone or a socket that was never up. It owes the same one Closed close does, so nothing is lost by a caller that counts them.

pub fn failed(reason: String) -> Socket

A socket for a dial that never got as far as answering, because the platform raised under it. It behaves like any other failed dial: no writes, and the reason out of the first poll.

pub fn finished(socket: Socket) -> Bool

Whether Closed has been reported and there is nothing left to do.

pub fn live(socket: Socket) -> Bool

Whether a write would be attempted. False from the moment the socket starts ending, which is one poll before finished can be true.

pub const max_buffered_bytes: Int

Bytes one socket will hold with no whole message out of them before it gives up on the peer. The same ceiling glyde/gateway puts on a reassembled payload, which is the largest thing that can arrive.

pub fn open(url: String) -> Socket

Dial a wss URL.

Blocks for up to dial_timeout. Nothing is reported here, a failed dial included: that comes out of the first poll.

pub fn poll(
  socket: Socket,
  timeout timeout: Int,
) -> #(Socket, List(transport.Event))

turn with the events handed back instead of reported.

pub fn send_bytes(socket: Socket, bytes: BitArray) -> Socket

Write one binary frame. Answers like send_text.

pub fn send_text(socket: Socket, text: String) -> Socket

Write one text frame. A socket whose write failed comes back ending, so live says whether it went out and the next poll says why it did not.

pub fn turn(
  socket: Socket,
  timeout timeout: Int,
  report report: fn(transport.Event) -> Nil,
) -> Socket

Read for up to timeout milliseconds and hand what came of it to report.

Stop once finished says so: a finished socket returns at once, so a loop that keeps going spins. The timeout bounds the whole read, so a peer that sends bytes without finishing a message cannot stretch it.

Search Document