-module(wist@adapters@mist). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/wist/adapters/mist.gleam"). -export([upgrade/3]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Wist adapter for the Mist web server.\n" "\n" " This module implements the transport adapter bridging Mist's WebSocket upgrade\n" " logic and connection actor loops to Wist's transport-neutral `Handler` API.\n" "\n" " Use `upgrade` inside your Mist HTTP request handler to switch protocols and\n" " run a stateful Wist handler.\n" ). -file("src/wist/adapters/mist.gleam", 212). -spec to_gramps_close_reason(gleam@option:option(wist:close_frame())) -> gramps@websocket:close_reason(). to_gramps_close_reason(Maybe_frame) -> case Maybe_frame of none -> {normal, <<>>}; {some, {close_frame, Code, Reason}} -> {custom_close_reason, Code, gleam_stdlib:identity(Reason)} end. -file("src/wist/adapters/mist.gleam", 180). -spec send_frame(mist@internal@websocket:websocket_connection(), wist:frame()) -> {ok, nil} | {error, nil}. send_frame(Connection, Frame) -> case Frame of {text, Text} -> _pipe = mist:send_text_frame(Connection, Text), gleam@result:replace_error(_pipe, nil); {binary, Bin} -> _pipe@1 = mist:send_binary_frame(Connection, Bin), gleam@result:replace_error(_pipe@1, nil); {ping, Data} -> Bytes = gramps@websocket:encode_ping_frame(Data, none), _pipe@2 = glisten@transport:send( erlang:element(3, Connection), erlang:element(2, Connection), Bytes ), gleam@result:replace_error(_pipe@2, nil); {pong, Data@1} -> Bytes@1 = gramps@websocket:encode_pong_frame(Data@1, none), _pipe@3 = glisten@transport:send( erlang:element(3, Connection), erlang:element(2, Connection), Bytes@1 ), gleam@result:replace_error(_pipe@3, nil); {close, Maybe_close} -> Reason = to_gramps_close_reason(Maybe_close), Bytes@2 = gramps@websocket:encode_close_frame(Reason, none), _pipe@4 = glisten@transport:send( erlang:element(3, Connection), erlang:element(2, Connection), Bytes@2 ), gleam@result:replace_error(_pipe@4, nil) end. -file("src/wist/adapters/mist.gleam", 151). -spec run_effects( list(wist:effect(NDB)), mist@internal@websocket:websocket_connection(), wist:codec(any(), NDB) ) -> {boolean(), list(wist:effect(NDB))}. run_effects(Effects, Connection, Codec) -> case Effects of [] -> {false, []}; [{close_connection, Maybe_close} | _] -> _ = send_frame(Connection, {close, Maybe_close}), {true, []}; [{send, Msg} | Rest] -> case (erlang:element(3, Codec))(Msg) of {ok, Frame} -> _ = send_frame(Connection, Frame), run_effects(Rest, Connection, Codec); {error, _} -> {true, []} end; [{send_frame, Frame@1} | Rest@1] -> _ = send_frame(Connection, Frame@1), run_effects(Rest@1, Connection, Codec) end. -file("src/wist/adapters/mist.gleam", 123). -spec process_inbound( NCR, wist:frame(), wist:handler(NCR, NCS, NCT), wist:codec(NCS, NCT), mist@internal@websocket:websocket_connection() ) -> mist:next(NCR, nil). process_inbound(State, Frame, Handler, Codec, Connection) -> Update_fn = wist:get_update(Handler), case (erlang:element(2, Codec))(Frame) of {ok, Decoded} -> {Next_state, Effects} = Update_fn(State, {message, Decoded}), {Should_close, _} = run_effects(Effects, Connection, Codec), case Should_close of true -> mist:stop(); false -> mist:continue(Next_state) end; {error, _} -> _ = Update_fn( State, {failed, {socket_error, <<"Frame decode failed"/utf8>>}} ), mist:stop() end. -file("src/wist/adapters/mist.gleam", 53). ?DOC( " Upgrades an incoming HTTP request to a WebSocket connection managed by Wist.\n" "\n" " If the request is a valid WebSocket handshake request, it returns a 101 Switching\n" " Protocols response body configured to spawn Mist's WebSocket handler process.\n" " If the request is malformed or invalid, a 400 Bad Request response is returned.\n" "\n" " ### Parameters\n" " - `request`: The incoming HTTP request carrying the Mist transport `Connection`.\n" " - `handler`: The stateful `wist.Handler` defining initial state and event transitions.\n" " - `codec`: A `wist.Codec` specifying how to encode and decode wire frames.\n" "\n" " ### Guarantees\n" " - **Serialization**: Connection events are processed strictly sequentially. `update` is never run concurrently.\n" " - **FIFO Effects**: Effects returned by `update` are executed in the exact order they are listed.\n" " - **Safety**: If frame decoding fails, the connection is closed immediately, and a `Failed` event is dispatched.\n" "\n" " ### Limitations\n" " - Outbound pings are supported via the `wist.Ping` effect, but client `Pong` frames are swallowed by Mist's internal wrapper and will not be dispatched as events.\n" "\n" " ### Example\n" " ```gleam\n" " import wist\n" " import wist/adapters/mist as wist_mist\n" "\n" " fn my_http_handler(req) {\n" " case req.path {\n" " \"/ws\" -> wist_mist.upgrade(req, my_ws_handler, wist.raw_codec())\n" " _ -> response.new(200)\n" " }\n" " }\n" " ```\n" ). -spec upgrade( gleam@http@request:request(mist@internal@http:connection()), wist:handler(any(), NCJ, NCK), wist:codec(NCJ, NCK) ) -> gleam@http@response:response(mist:response_data()). upgrade(Request, Handler, Codec) -> Context = {context, erlang:element(8, Request), erlang:element(3, Request), erlang:element(9, Request)}, On_init = fun(Connection) -> Init_state_fn = wist:get_init_state(Handler), Update_fn = wist:get_update(Handler), User_state = Init_state_fn(Context), {User_state@1, Opened_effects} = Update_fn(User_state, opened), {Should_close, _} = run_effects(Opened_effects, Connection, Codec), case Should_close of true -> _ = glisten@transport:close( erlang:element(3, Connection), erlang:element(2, Connection) ), nil; false -> nil end, {User_state@1, none} end, Mist_handler = fun(State, Message, Connection@1) -> case Message of {text, Text} -> Frame = {text, Text}, process_inbound(State, Frame, Handler, Codec, Connection@1); {binary, Bin} -> Frame@1 = {binary, Bin}, process_inbound(State, Frame@1, Handler, Codec, Connection@1); _ -> mist:continue(State) end end, On_close = fun(State@1) -> Update_fn@1 = wist:get_update(Handler), _ = Update_fn@1(State@1, {closed, unknown}), nil end, mist:websocket(Request, Mist_handler, On_init, On_close).