diff --git a/README.md b/README.md index c3cc05e..120a858 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ pub fn main() { stratus.Binary(_msg) -> stratus.continue(state) stratus.User(Close) -> { let assert Ok(_) = - stratus.close_with_reason(conn, stratus.GoingAway(<<"goodbye">>)) + stratus.close(conn, stratus.GoingAway(<<"goodbye!">>)) stratus.stop() } } diff --git a/src/stratus.gleam b/src/stratus.gleam index ae14b60..63f1e84 100644 --- a/src/stratus.gleam +++ b/src/stratus.gleam @@ -1,5 +1,6 @@ import exception import gleam/bit_array +import gleam/bool import gleam/bytes_tree.{type BytesTree} import gleam/crypto import gleam/erlang/charlist @@ -72,6 +73,11 @@ pub type SocketReason { Exbadseq } +pub type CustomCloseError { + SocketFail(SocketReason) + InvalidCode +} + fn convert_socket_reason(reason: socket.SocketReason) -> SocketReason { case reason { socket.Badarg -> Badarg @@ -267,7 +273,7 @@ pub type InitializationError { HandshakeFailed(HandshakeError) // The actor failed to start, most likely due to a timeout in your `init` ActorFailed(actor.StartError) - // + // FailedToTransferSocket(SocketReason) } @@ -669,25 +675,38 @@ pub fn send_ping(conn: Connection, data: BitArray) -> Result(Nil, SocketReason) |> result.map_error(convert_socket_reason) } -/// This will close the WebSocket connection. -pub fn close(conn: Connection) -> Result(Nil, SocketReason) { - close_with_reason(conn, Normal(body: <<>>)) -} - pub type CloseReason { + NotProvided + /// Status code: 1000 Normal(body: BitArray) + /// Status code: 1001 GoingAway(body: BitArray) + /// Status code: 1002 ProtocolError(body: BitArray) + /// Status code: 1003 UnexpectedDataType(body: BitArray) + /// Status code: 1007 InconsistentDataType(body: BitArray) + /// Status code: 1008 PolicyViolation(body: BitArray) + /// Status code: 1009 MessageTooBig(body: BitArray) + /// Status code: 1010 MissingExtensions(body: BitArray) + /// Status code: 1011 UnexpectedCondition(body: BitArray) + /// Use [`close_custom`](#close_custom) to send a custom close code. + Custom(CustomCloseReason) +} + +/// Use [`close_custom`](#close_custom) to send a custom close code. +pub opaque type CustomCloseReason { + CustomCloseReason(code: Int, body: BitArray) } fn convert_close_reason(reason: CloseReason) -> websocket.CloseReason { case reason { + NotProvided -> websocket.NotProvided GoingAway(body:) -> websocket.GoingAway(body:) InconsistentDataType(body:) -> websocket.InconsistentDataType(body:) MessageTooBig(body:) -> websocket.MessageTooBig(body:) @@ -697,13 +716,29 @@ fn convert_close_reason(reason: CloseReason) -> websocket.CloseReason { ProtocolError(body:) -> websocket.ProtocolError(body:) UnexpectedCondition(body:) -> websocket.UnexpectedCondition(body:) UnexpectedDataType(body:) -> websocket.UnexpectedDataType(body:) + Custom(CustomCloseReason(code:, body:)) -> + websocket.CustomCloseReason(code:, body:) } } -/// This closes the WebSocket connection with a particular close reason. -pub fn close_with_reason( +/// Closes the connection with a custom close code between 1000 and 4999. +pub fn close_custom( + conn: Connection, + code code: Int, + body body: BitArray, +) -> Result(Nil, CustomCloseError) { + use <- bool.guard( + when: code >= 5000 || code < 1000, + return: Error(InvalidCode), + ) + + close(conn, Custom(CustomCloseReason(code:, body:))) + |> result.map_error(SocketFail) +} + +pub fn close( conn: Connection, - reason: CloseReason, + because reason: CloseReason, ) -> Result(Nil, SocketReason) { let reason = convert_close_reason(reason) let mask = crypto.strong_random_bytes(4)