-module(sprocket). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([new/4, handle_ws/2, cleanup/1, render_html/1]). -export_type([sprocket_opts/0, sprocket/0, payload/0, response/0, connect_error/0]). -type sprocket_opts() :: {sprocket_opts, boolean()}. -type sprocket() :: {sprocket, gleam@option:option(gleam@erlang@process:subject(sprocket@runtime:message())), sprocket@context:element(), fun((binary()) -> {ok, nil} | {error, nil}), fun((binary()) -> {ok, nil} | {error, nil}), gleam@option:option(sprocket_opts())}. -type payload() :: {join_payload, binary()} | {event_payload, binary(), binary(), gleam@option:option(binary())} | {hook_event_payload, binary(), binary(), gleam@option:option(gleam@dynamic:dynamic_())} | {empty_payload, gleam@option:option(binary())}. -type response() :: {joined, sprocket()} | empty. -type connect_error() :: connect_error | preflight_not_found | invalid_csrf_token. -spec new( sprocket@context:element(), fun((binary()) -> {ok, nil} | {error, nil}), fun((binary()) -> {ok, nil} | {error, nil}), gleam@option:option(sprocket_opts()) ) -> sprocket(). new(View, Ws_send, Csrf_validator, Opts) -> {sprocket, none, View, Ws_send, Csrf_validator, Opts}. -spec require_runtime( sprocket(), fun((gleam@erlang@process:subject(sprocket@runtime:message())) -> {ok, LQL} | {error, binary()}) ) -> {ok, LQL} | {error, binary()}. require_runtime(Spkt, Cb) -> case Spkt of {sprocket, {some, Runtime}, _, _, _, _} -> Cb(Runtime); _ -> {error, <<"Sprocket runtime not found. Runtime must be started before handling messages"/utf8>>} end. -spec decode_join(gleam@dynamic:dynamic_()) -> {ok, {binary(), payload()}} | {error, list(gleam@dynamic:decode_error())}. decode_join(Data) -> _pipe = Data, (gleam@dynamic:tuple2( fun gleam@dynamic:string/1, gleam@dynamic:decode1( fun(Field@0) -> {join_payload, Field@0} end, gleam@dynamic:field(<<"csrf"/utf8>>, fun gleam@dynamic:string/1) ) ))(_pipe). -spec decode_event(gleam@dynamic:dynamic_()) -> {ok, {binary(), payload()}} | {error, list(gleam@dynamic:decode_error())}. decode_event(Data) -> _pipe = Data, (gleam@dynamic:tuple2( fun gleam@dynamic:string/1, gleam@dynamic:decode3( fun(Field@0, Field@1, Field@2) -> {event_payload, Field@0, Field@1, Field@2} end, gleam@dynamic:field(<<"kind"/utf8>>, fun gleam@dynamic:string/1), gleam@dynamic:field(<<"id"/utf8>>, fun gleam@dynamic:string/1), gleam@dynamic:optional_field( <<"value"/utf8>>, fun gleam@dynamic:string/1 ) ) ))(_pipe). -spec decode_hook_event(gleam@dynamic:dynamic_()) -> {ok, {binary(), payload()}} | {error, list(gleam@dynamic:decode_error())}. decode_hook_event(Data) -> _pipe = Data, (gleam@dynamic:tuple2( fun gleam@dynamic:string/1, gleam@dynamic:decode3( fun(Field@0, Field@1, Field@2) -> {hook_event_payload, Field@0, Field@1, Field@2} end, gleam@dynamic:field(<<"id"/utf8>>, fun gleam@dynamic:string/1), gleam@dynamic:field(<<"name"/utf8>>, fun gleam@dynamic:string/1), gleam@dynamic:optional_field( <<"payload"/utf8>>, fun gleam@dynamic:dynamic/1 ) ) ))(_pipe). -spec decode_empty(gleam@dynamic:dynamic_()) -> {ok, {binary(), payload()}} | {error, list(gleam@dynamic:decode_error())}. decode_empty(Data) -> _pipe = Data, (gleam@dynamic:tuple2( fun gleam@dynamic:string/1, gleam@dynamic:decode1( fun(Field@0) -> {empty_payload, Field@0} end, gleam@dynamic:optional_field( <<"nothing"/utf8>>, fun gleam@dynamic:string/1 ) ) ))(_pipe). -spec update_to_json(sprocket@runtime:rendered_update(), boolean()) -> gleam@json:json(). update_to_json(Update, Debug) -> case Update of {patch_update, P} -> gleam@json:preprocessed_array( [gleam@json:string(<<"update"/utf8>>), sprocket@internal@patch:patch_to_json(P, Debug), gleam@json:object( [{<<"debug"/utf8>>, gleam@json:bool(Debug)}] )] ); {full_update, Update@1} -> sprocket@internal@render:renderer( sprocket@internal@renderers@json:json_renderer(), fun(Render_json) -> gleam@json:preprocessed_array( [gleam@json:string(<<"ok"/utf8>>), Render_json(Update@1)] ) end ) end. -spec hook_event_to_json(binary(), binary(), gleam@option:option(binary())) -> binary(). hook_event_to_json(Id, Event, Payload) -> _pipe = gleam@json:preprocessed_array( [gleam@json:string(<<"hook:event"/utf8>>), case Payload of {some, Payload@1} -> gleam@json:object( [{<<"id"/utf8>>, gleam@json:string(Id)}, {<<"kind"/utf8>>, gleam@json:string(Event)}, {<<"payload"/utf8>>, gleam@json:string(Payload@1)}] ); none -> gleam@json:object( [{<<"id"/utf8>>, gleam@json:string(Id)}, {<<"kind"/utf8>>, gleam@json:string(Event)}] ) end] ), gleam@json:to_string(_pipe). -spec connect(sprocket()) -> {ok, gleam@erlang@process:subject(sprocket@runtime:message())} | {error, nil}. connect(Spkt) -> Debug = begin _pipe = gleam@option:map( erlang:element(6, Spkt), fun(Opts) -> erlang:element(2, Opts) end ), gleam@option:unwrap(_pipe, false) end, Updater = {updater, fun(Update) -> _pipe@1 = update_to_json(Update, Debug), _pipe@2 = gleam@json:to_string(_pipe@1), (erlang:element(4, Spkt))(_pipe@2) end}, Dispatcher = {dispatcher, fun(Id, Event, Payload) -> _ = begin _pipe@3 = hook_event_to_json(Id, Event, Payload), (erlang:element(4, Spkt))(_pipe@3) end, {ok, nil} end}, case sprocket@runtime:start( erlang:element(3, Spkt), Updater, {some, Dispatcher} ) of {ok, R} -> sprocket@runtime:render_update(R), {ok, R}; {error, _} -> sprocket@internal@logger:error(<<"Error starting runtime"/utf8>>), {error, nil} end. -spec error_to_json(connect_error()) -> binary(). error_to_json(Error) -> _pipe = gleam@json:preprocessed_array( [gleam@json:string(<<"error"/utf8>>), case Error of connect_error -> gleam@json:object( [{<<"code"/utf8>>, gleam@json:string(<<"connect_error"/utf8>>)}, {<<"msg"/utf8>>, gleam@json:string( <<"Unable to connect to session"/utf8>> )}] ); preflight_not_found -> gleam@json:object( [{<<"code"/utf8>>, gleam@json:string( <<"preflight_not_found"/utf8>> )}, {<<"msg"/utf8>>, gleam@json:string(<<"No preflight found"/utf8>>)}] ); invalid_csrf_token -> gleam@json:object( [{<<"code"/utf8>>, gleam@json:string(<<"invalid_csrf_token"/utf8>>)}, {<<"msg"/utf8>>, gleam@json:string(<<"Invalid CSRF token"/utf8>>)}] ) end] ), gleam@json:to_string(_pipe). -spec handle_ws(sprocket(), binary()) -> {ok, response()} | {error, binary()}. handle_ws(Spkt, Msg) -> case gleam@json:decode( Msg, gleam@dynamic:any( [fun decode_join/1, fun decode_event/1, fun decode_hook_event/1, fun decode_empty/1] ) ) of {ok, {<<"join"/utf8>>, {join_payload, Csrf}}} -> case (erlang:element(5, Spkt))(Csrf) of {ok, _} -> case connect(Spkt) of {ok, Runtime} -> {ok, {joined, erlang:setelement(2, Spkt, {some, Runtime})}}; {error, _} -> {error, <<"Error connecting to runtime"/utf8>>} end; {error, _} -> sprocket@internal@logger:error( <<"Invalid CSRF token"/utf8>> ), _ = begin _pipe = invalid_csrf_token, _pipe@1 = error_to_json(_pipe), (erlang:element(4, Spkt))(_pipe@1) end, {error, <<"Invalid CSRF token"/utf8>>} end; {ok, {<<"event"/utf8>>, {event_payload, Kind, Event_id, Value}}} -> sprocket@internal@logger:debug( <<<<<<"Event: "/utf8, Kind/binary>>/binary, " "/utf8>>/binary, Event_id/binary>> ), require_runtime( Spkt, fun(Runtime@1) -> sprocket@runtime:process_event(Runtime@1, Event_id, Value), {ok, empty} end ); {ok, {<<"hook:event"/utf8>>, {hook_event_payload, Id, Event, Payload}}} -> sprocket@internal@logger:debug( <<<<<<"Hook Event: "/utf8, Event/binary>>/binary, " "/utf8>>/binary, Id/binary>> ), require_runtime( Spkt, fun(Runtime@2) -> Reply_dispatcher = fun(Event@1, Payload@1) -> _pipe@2 = hook_event_to_json(Id, Event@1, Payload@1), (erlang:element(4, Spkt))(_pipe@2) end, sprocket@runtime:process_client_hook( Runtime@2, Id, Event, Payload, Reply_dispatcher ), {ok, empty} end ); {error, _} -> sprocket@internal@logger:error( <<"Error decoding message: "/utf8, Msg/binary>> ), {error, <<"Error decoding message"/utf8>>}; _ -> sprocket@internal@logger:error( <<"Unexpected payload type: "/utf8, Msg/binary>> ), {error, <<"Unexpected payload type"/utf8>>} end. -spec cleanup(sprocket()) -> gleam@option:option(nil). cleanup(Spkt) -> _pipe = erlang:element(2, Spkt), gleam@option:map(_pipe, fun(R) -> sprocket@runtime:stop(R) end). -spec render_html(sprocket@context:element()) -> binary(). render_html(El) -> sprocket@internal@render:renderer( sprocket@internal@renderers@html:html_renderer(), fun(Render_html) -> _assert_subject = begin _pipe = ids@cuid:start(), gleam@result:map_error( _pipe, fun(Error) -> sprocket@internal@logger:error( <<"render.render: Failed to start cuid channel"/utf8>> ), Error end ) end, {ok, Cuid_channel} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"sprocket"/utf8>>, function => <<"render_html"/utf8>>, line => 306}) end, Ctx = sprocket@context:new( El, Cuid_channel, none, fun() -> nil end, fun(_, _) -> nil end ), {reconciled_result, _, Reconciled} = sprocket@internal@reconcilers@recursive:reconcile( Ctx, El, none, none ), Render_html(Reconciled) end ).