···11# Changelog
2233+# Unreleased
44+55+- Add `send_close_frame` function for the WebSocket API
66+- Add `CloseCode` for various close frames
77+38# v2.0.3 - 06.11.2025
49510- Replace `gramps` with `websocks` package
+1-1
manifest.toml
···21212222[requirements]
2323compresso = { version = "0.1.0" }
2424+gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" }
2425gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" }
2526gleam_http = { version = ">= 4.3.0 and < 5.0.0" }
2627gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" }
···3132glisten = { version = ">= 8.0.1 and < 9.0.0" }
3233logging = { version = ">= 1.3.0 and < 2.0.0" }
3334websocks = { version = ">= 2.0.0 and < 3.0.0" }
3434-gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" }
+87
src/ewe.gleam
···6262//// "upgrade_websocket",
6363//// "send_binary_frame",
6464//// "send_text_frame",
6565+//// "send_close_frame",
6566//// "websocket_continue",
6667//// "websocket_continue_with_selector",
6768//// "websocket_stop",
···864865 WebsocketAbnormalStop(reason)
865866}
866867868868+fn to_websocket_next(
869869+ next: websocket.WebsocketNext(user_state, user_message),
870870+) -> WebsocketNext(user_state, user_message) {
871871+ case next {
872872+ websocket.Continue(user_state, selector) ->
873873+ WebsocketContinue(user_state, selector)
874874+ websocket.NormalStop -> WebsocketNormalStop
875875+ websocket.AbnormalStop(reason) -> WebsocketAbnormalStop(reason)
876876+ }
877877+}
878878+867879fn to_internal_websocket_next(
868880 next: WebsocketNext(user_state, user_message),
869881) -> websocket.WebsocketNext(user_state, user_message) {
···9941006 conn.context,
9951007 bit_array.from_string(text),
9961008 )
10091009+}
10101010+10111011+/// WebSocket close codes that can be sent when closing a connection. The `data`
10121012+/// parameter allows you to include payload up to 123 bytes in size.
10131013+///
10141014+pub type CloseCode {
10151015+ /// Standard graceful shutdown (1000). Use when connection completed
10161016+ /// successfully.
10171017+ ///
10181018+ NormalClosure(data: String)
10191019+ /// Invalid message format (1007). Received payload that doesn't match what
10201020+ /// you expected.
10211021+ ///
10221022+ InvalidPayloadData(data: String)
10231023+ /// Application policy violation (1008).Client broke your rules - failed
10241024+ /// authentication, hit rate limits, or violated business logic.
10251025+ ///
10261026+ PolicyViolation(data: String)
10271027+ /// Message exceeds size limits (1009). Client sent something bigger than
10281028+ /// your application allows.
10291029+ ///
10301030+ MessageTooBig(data: String)
10311031+ /// Server encountered unexpected error (1011). Something went wrong on your
10321032+ /// side that prevents handling the connection.
10331033+ ///
10341034+ InternalError(data: String)
10351035+ /// Server is restarting (1012). Planned restart - clients can reconnect
10361036+ /// after a bit.
10371037+ ///
10381038+ ServiceRestart(data: String)
10391039+ /// Temporary server overload (1013). Use when server is temporarily
10401040+ /// unavailable, client should retry.
10411041+ ///
10421042+ TryAgainLater(data: String)
10431043+ /// Gateway/proxy received invalid response (1014). You're acting as a proxy
10441044+ /// and the upstream server gave you garbage.
10451045+ ///
10461046+ BadGateway(data: String)
10471047+ /// Custom close codes 3000-4999 for application-specific use.
10481048+ ///
10491049+ CustomCloseCode(code: Int, data: String)
10501050+ /// Close without a specific reason.
10511051+ ///
10521052+ NoCloseReason
10531053+}
10541054+10551055+fn to_internal_close_code(code: CloseCode) -> websocks.CloseReason {
10561056+ case code {
10571057+ NormalClosure(data) -> websocks.NormalClosure(bit_array.from_string(data))
10581058+ InvalidPayloadData(data) ->
10591059+ websocks.InvalidPayloadData(bit_array.from_string(data))
10601060+ PolicyViolation(data) ->
10611061+ websocks.PolicyViolation(bit_array.from_string(data))
10621062+ MessageTooBig(data) -> websocks.MessageTooBig(bit_array.from_string(data))
10631063+ InternalError(data) -> websocks.InternalError(bit_array.from_string(data))
10641064+ ServiceRestart(data) -> websocks.ServiceRestart(bit_array.from_string(data))
10651065+ TryAgainLater(data) -> websocks.TryAgainLater(bit_array.from_string(data))
10661066+ BadGateway(data) -> websocks.BadGateway(bit_array.from_string(data))
10671067+ CustomCloseCode(code, data) ->
10681068+ websocks.CustomCloseCode(code, bit_array.from_string(data))
10691069+ NoCloseReason -> websocks.NoCloseReason
10701070+ }
10711071+}
10721072+10731073+/// Sends a close frame to the websocket client. Once this function is called,
10741074+/// no other frames can be sent on this connection. Returns how the WebSocket
10751075+/// connection should proceed - make sure your handler returns this value.
10761076+///
10771077+pub fn send_close_frame(
10781078+ conn: WebsocketConnection,
10791079+ code: CloseCode,
10801080+) -> WebsocketNext(user_state, user_message) {
10811081+ to_internal_close_code(code)
10821082+ |> websocket.send_close_frame(conn.transport, conn.socket, _)
10831083+ |> to_websocket_next()
9971084}
99810859991086// -----------------------------------------------------------------------------
+28
src/ewe/internal/stream/websocket.gleam
···259259 }
260260}
261261262262+pub fn send_close_frame(
263263+ transport: Transport,
264264+ socket: Socket,
265265+ code: websocks.CloseReason,
266266+) -> WebsocketNext(user_state, user_message) {
267267+ let frame =
268268+ exception.rescue(fn() {
269269+ websocks.encode_close_frame(code, None)
270270+ |> bytes_tree.from_bit_array()
271271+ |> transport.send(transport, socket, _)
272272+ })
273273+274274+ case frame {
275275+ Ok(Ok(Nil)) -> NormalStop
276276+ Ok(Error(reason)) ->
277277+ AbnormalStop("Failed to send close frame: " <> string.inspect(reason))
278278+ Error(reason) -> {
279279+ logging.log(
280280+ logging.Error,
281281+ "Frame should be sent from the WebSocket connection, but was sent from different process: "
282282+ <> string.inspect(reason),
283283+ )
284284+285285+ panic as non_owning_process
286286+ }
287287+ }
288288+}
289289+262290// -----------------------------------------------------------------------------
263291// MESSAGE HANDLING
264292// -----------------------------------------------------------------------------