Current section
Files
Jump to
Current section
Files
src/sprocket.erl
-module(sprocket).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([new/4, handle_ws/2, cleanup/1, render/2]).
-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, LEM} |
{error, binary()})
) -> {ok, LEM} | {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@render:renderer(
sprocket@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(sprocket@context:element(), sprocket@render:renderer(LFA)) -> LFA.
render(El, R) ->
sprocket@render:renderer(
R,
fun(Render) ->
_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"/utf8>>,
line => 305})
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(Reconciled)
end
).