-module(omnimessage@server). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([pipe/3, application/3, component/4, start_server_component/3, wisp_http_middleware/5, mist_websocket_pipe/4, mist_websocket_application/4]). -export_type([encoder_decoder/3, app/5, websocket_state/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type encoder_decoder(AAOU, AAOV, AAOW) :: {encoder_decoder, fun((AAOU) -> {ok, AAOV} | {error, nil}), fun((AAOV) -> {ok, AAOU} | {error, AAOW})}. -opaque app(AAOX, AAOY, AAOZ, AAPA, AAPB) :: {app, fun((AAOX) -> {AAOY, lustre@effect:effect(AAOZ)}), fun((AAOY, AAOZ) -> {AAOY, lustre@effect:effect(AAOZ)}), gleam@option:option(list(lustre@component:option(AAOZ))), encoder_decoder(AAOZ, AAPA, AAPB)}. -type websocket_state(AAPC) :: {websocket_state, lustre:runtime(AAPC), gleam@erlang@process:subject(AAPC), gleam@erlang@process:subject(lustre@runtime@transport:client_message(AAPC))}. -file("src/omnimessage/server.gleam", 78). ?DOC( " A utility function for easily handling messages:\n" "\n" " ```gleam\n" " let out_msg = pipe(in_msg, encoder_decoder, handler)\n" " ```\n" ). -spec pipe(AAPD, encoder_decoder(AAPE, AAPD, AAPF), fun((AAPE) -> AAPE)) -> {ok, gleam@option:option(AAPD)} | {error, AAPF}. pipe(Msg, Encoder_decoder, Handler) -> _pipe = Msg, _pipe@1 = (erlang:element(3, Encoder_decoder))(_pipe), _pipe@2 = gleam@result:map(_pipe@1, Handler), _pipe@3 = gleam@result:map(_pipe@2, erlang:element(2, Encoder_decoder)), gleam@result:map(_pipe@3, fun gleam@option:from_result/1). -file("src/omnimessage/server.gleam", 105). ?DOC( " This creates a version of a Lustre application that can be used in\n" " `omnimessage/server.start_actor` (see below). A view is not necessary, as\n" " this application will never render anything.\n" ). -spec application( fun((AAPM) -> {AAPN, lustre@effect:effect(AAPO)}), fun((AAPN, AAPO) -> {AAPN, lustre@effect:effect(AAPO)}), encoder_decoder(AAPO, AAPR, AAPS) ) -> app(AAPM, AAPN, AAPO, AAPR, AAPS). application(Init, Update, Encoder_decoder) -> {app, Init, Update, none, Encoder_decoder}. -file("src/omnimessage/server.gleam", 113). -spec component( fun((AAQB) -> {AAQC, lustre@effect:effect(AAQD)}), fun((AAQC, AAQD) -> {AAQC, lustre@effect:effect(AAQD)}), list(lustre@component:option(AAQD)), encoder_decoder(AAQD, AAQI, AAQJ) ) -> app(AAQB, AAQC, AAQD, AAQI, AAQJ). component(Init, Update, Options, Encoder_decoder) -> {app, Init, Update, {some, Options}, Encoder_decoder}. -file("src/omnimessage/server.gleam", 128). ?DOC( " This is a beefed up version of `lustre.start_actor` that allows subscribing\n" " to messages dispatched inside the runtime.\n" "\n" " This is what enables using a Lustre server component for communication,\n" " powering `mist_websocket_application()` below.\n" ). -spec start_server_component( app(AAQS, any(), AAQU, any(), any()), AAQS, fun((AAQU) -> nil) ) -> {ok, lustre:runtime(AAQU)} | {error, lustre:error()}. start_server_component(App, Start_args, Listener) -> Wrapped_update = fun(Model, Msg) -> Listener(Msg), (erlang:element(3, App))(Model, Msg) end, View = fun(_) -> lustre@element:none() end, Lustre_app = case erlang:element(4, App) of none -> lustre:application(erlang:element(2, App), Wrapped_update, View); {some, Options} -> lustre:component( erlang:element(2, App), Wrapped_update, View, Options ) end, lustre:start_server_component(Lustre_app, Start_args). -file("src/omnimessage/server.gleam", 156). ?DOC( " A wisp middleware to automatically handle HTTP POST omnimessage messages.\n" "\n" " - `req` The wisp request\n" " - `path` The path to which messages are POSTed\n" " - `encoder_decoder` For encoding and decoding messages\n" " - `handler` For handling the incoming messages\n" "\n" " See a full example using this in the Readme or in the examples folder.\n" ). -spec wisp_http_middleware( gleam@http@request:request(wisp@internal:connection()), binary(), encoder_decoder(AAVJ, binary(), any()), fun((AAVJ) -> AAVJ), fun(() -> gleam@http@response:response(wisp:body())) ) -> gleam@http@response:response(wisp:body()). wisp_http_middleware(Req, Path, Encoder_decoder, Handler, Fun) -> case {erlang:element(8, Req) =:= Path, erlang:element(2, Req)} of {true, post} -> wisp:require_string_body( Req, fun(Req_body) -> case begin _pipe = Req_body, pipe(_pipe, Encoder_decoder, Handler) end of {ok, {some, Res_body}} -> _pipe@1 = wisp:response(200), wisp:string_body(_pipe@1, Res_body); {ok, none} -> wisp:response(200); {error, _} -> wisp:unprocessable_entity() end end ); {_, _} -> Fun() end. -file("src/omnimessage/server.gleam", 191). ?DOC( " A mist websocket handler to automatically respond to omnimessage messages.\n" "\n" " Return this as a response to the websocket init request.\n" "\n" " - `req` The mist request\n" " - `encoder_decoder` For encoding and decoding messages\n" " - `handler` For handling the incoming messages\n" " - `on_error` For handling decode errors\n" "\n" " See a full example using this in the Readme or in the examples folder.\n" ). -spec mist_websocket_pipe( gleam@http@request:request(mist@internal@http:connection()), encoder_decoder(AARI, binary(), AARJ), fun((AARI) -> AARI), fun((AARJ) -> nil) ) -> gleam@http@response:response(mist:response_data()). mist_websocket_pipe(Req, Encoder_decoder, Handler, On_error) -> mist:websocket(Req, fun(Runtime, Conn, Msg) -> case Msg of {text, Msg@1} -> _ = case pipe(Msg@1, Encoder_decoder, Handler) of {ok, {some, Encoded_msg}} -> mist:send_text_frame(Conn, Encoded_msg); {ok, none} -> {ok, nil}; {error, Decode_error} -> {ok, On_error(Decode_error)} end, gleam@otp@actor:continue(Runtime); {binary, _} -> gleam@otp@actor:continue(Runtime); {custom, _} -> gleam@otp@actor:continue(Runtime); closed -> {stop, normal}; shutdown -> {stop, normal} end end, fun(_) -> {none, none} end, fun(_) -> nil end). -file("src/omnimessage/server.gleam", 244). ?DOC( " A mist websocket handler to automatically respond to omnimessage messages\n" " via a Lustre server component. The server component can then be used\n" " similarly to one created by an `omnimessage/lustre` and handle the messages\n" " via update, dispatch, and effects.\n" "\n" " Return this as a response to the websocket init request.\n" "\n" " - `req` The mist request\n" " - `app` An application created with `omnimessage/server.application`\n" " - `flags` Flags to hand to the application's `init`\n" " - `on_error` For handling decode errors\n" "\n" " See a full example using this in the Readme or in the examples folder.\n" ). -spec mist_websocket_application( gleam@http@request:request(mist@internal@http:connection()), app(AARP, any(), any(), binary(), AARS), AARP, fun((AARS) -> nil) ) -> gleam@http@response:response(mist:response_data()). mist_websocket_application(Req, App, Flags, On_error) -> mist:websocket(Req, fun(State, Conn, Msg) -> case Msg of {text, Msg@1} -> case (erlang:element(3, erlang:element(5, App)))(Msg@1) of {ok, Decoded_msg} -> gleam@erlang@process:send( erlang:element(2, State), lustre:dispatch(Decoded_msg) ); {error, Decode_error} -> On_error(Decode_error) end, gleam@otp@actor:continue(State); {binary, _} -> gleam@otp@actor:continue(State); {custom, Msg@2} -> case case (erlang:element(2, erlang:element(5, App)))(Msg@2) of {ok, Msg@3} -> mist:send_text_frame(Conn, Msg@3); {error, _} -> {ok, nil} end of {ok, _} -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"omnimessage/server"/utf8>>, function => <<"mist_websocket_application"/utf8>>, line => 281}) end, gleam@otp@actor:continue(State); closed -> _pipe = lustre@server_component:deregister_subject( erlang:element(4, State) ), gleam@erlang@process:send(erlang:element(2, State), _pipe), {stop, normal}; shutdown -> _pipe = lustre@server_component:deregister_subject( erlang:element(4, State) ), gleam@erlang@process:send(erlang:element(2, State), _pipe), {stop, normal} end end, fun(_) -> Omni_self = gleam@erlang@process:new_subject(), Lustre_self = gleam@erlang@process:new_subject(), Runtime@1 = case start_server_component( App, Flags, fun(_capture) -> gleam@erlang@process:send(Omni_self, _capture) end ) of {ok, Runtime} -> Runtime; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail@1, module => <<"omnimessage/server"/utf8>>, function => <<"mist_websocket_application"/utf8>>, line => 255}) end, State@1 = {websocket_state, Runtime@1, Omni_self, Lustre_self}, {State@1, {some, begin _pipe@1 = gleam_erlang_ffi:new_selector(), gleam@erlang@process:selecting( _pipe@1, Omni_self, fun gleam@function:identity/1 ) end}} end, fun(State@2) -> _pipe@2 = lustre@server_component:deregister_subject( erlang:element(4, State@2) ), gleam@erlang@process:send(erlang:element(2, State@2), _pipe@2) end).