Current section

Files

Jump to
sprocket src sprocket@cassette.erl
Raw

src/sprocket@cassette.erl

-module(sprocket@cassette).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
-export([start/2, stop/1, get_state/1, push_sprocket/2, get_sprocket/2, pop_sprocket/2, client_message/5]).
-export_type([state/0, message/0, cassette_opts/0, payload/0, connect_error/0]).
-type state() :: {state,
list(gleam@erlang@process:subject(sprocket@sprocket:message())),
boolean(),
fun((binary()) -> {ok, nil} | {error, nil}),
gleam@erlang@process:subject(ids@cuid:message())}.
-type message() :: shutdown |
{get_state, gleam@erlang@process:subject(state())} |
{push_sprocket, gleam@erlang@process:subject(sprocket@sprocket:message())} |
{get_sprocket,
gleam@erlang@process:subject({ok,
gleam@erlang@process:subject(sprocket@sprocket:message())} |
{error, nil}),
sprocket@internal@utils@unique:unique()} |
{pop_sprocket,
gleam@erlang@process:subject({ok,
gleam@erlang@process:subject(sprocket@sprocket:message())} |
{error, nil}),
sprocket@internal@utils@unique:unique()}.
-type cassette_opts() :: {cassette_opts, boolean()}.
-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 connect_error() :: connect_error |
preflight_not_found |
invalid_csrf_token.
-spec handle_message(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_message(Message, State) ->
case Message of
shutdown ->
{stop, normal};
{get_state, Reply_with} ->
gleam@erlang@process:send(Reply_with, State),
gleam@otp@actor:continue(State);
{push_sprocket, Sprocket} ->
Updated_sprockets = gleam@list:reverse(
[Sprocket | gleam@list:reverse(erlang:element(2, State))]
),
gleam@otp@actor:continue(
erlang:setelement(2, State, Updated_sprockets)
);
{get_sprocket, Reply_with@1, Id} ->
Spkt = gleam@list:find(
erlang:element(2, State),
fun(S) -> case sprocket@sprocket:get_id(S) of
{ok, Spkt_id} ->
sprocket@internal@utils@unique:equals(Spkt_id, Id);
{error, _} ->
false
end end
),
gleam@erlang@process:send(Reply_with@1, Spkt),
gleam@otp@actor:continue(State);
{pop_sprocket, Reply_with@2, Id@1} ->
Sprocket@1 = gleam@list:find(
erlang:element(2, State),
fun(S@1) -> case sprocket@sprocket:get_id(S@1) of
{ok, Spkt_id@1} ->
sprocket@internal@utils@unique:equals(
Spkt_id@1,
Id@1
);
{error, _} ->
false
end end
),
gleam@erlang@process:send(Reply_with@2, Sprocket@1),
case Sprocket@1 of
{ok, Sprocket@2} ->
sprocket@sprocket:stop(Sprocket@2),
Updated_sprockets@1 = gleam@list:filter(
erlang:element(2, State),
fun(S@2) -> Sprocket@2 /= S@2 end
),
New_state = erlang:setelement(2, State, Updated_sprockets@1),
gleam@otp@actor:continue(New_state);
{error, _} ->
gleam@otp@actor:continue(State)
end
end.
-spec start(
fun((binary()) -> {ok, nil} | {error, nil}),
gleam@option:option(cassette_opts())
) -> gleam@erlang@process:subject(message()).
start(Csrf_validator, Opts) ->
_assert_subject = ids@cuid:start(),
{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/cassette"/utf8>>,
function => <<"start"/utf8>>,
line => 122})
end,
_assert_subject@1 = gleam@otp@actor:start(
{state,
[],
begin
_pipe = gleam@option:map(
Opts,
fun(Opts@1) -> erlang:element(2, Opts@1) end
),
gleam@option:unwrap(_pipe, false)
end,
Csrf_validator,
Cuid_channel},
fun handle_message/2
),
{ok, Ca} = case _assert_subject@1 of
{ok, _} -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"sprocket/cassette"/utf8>>,
function => <<"start"/utf8>>,
line => 123})
end,
Ca.
-spec stop(gleam@erlang@process:subject(message())) -> nil.
stop(Ca) ->
gleam@erlang@process:send(Ca, shutdown).
-spec get_state(gleam@erlang@process:subject(message())) -> state().
get_state(Ca) ->
gleam@erlang@process:call(
Ca,
fun(_capture) -> {get_state, _capture} end,
sprocket@internal@constants:call_timeout()
).
-spec push_sprocket(
gleam@erlang@process:subject(message()),
gleam@erlang@process:subject(sprocket@sprocket:message())
) -> nil.
push_sprocket(Ca, Sprocket) ->
gleam@erlang@process:send(Ca, {push_sprocket, Sprocket}).
-spec get_sprocket(
gleam@erlang@process:subject(message()),
sprocket@internal@utils@unique:unique()
) -> {ok, gleam@erlang@process:subject(sprocket@sprocket:message())} |
{error, nil}.
get_sprocket(Ca, Ws) ->
gleam@erlang@process:call(
Ca,
fun(_capture) -> {get_sprocket, _capture, Ws} end,
sprocket@internal@constants:call_timeout()
).
-spec pop_sprocket(
gleam@erlang@process:subject(message()),
sprocket@internal@utils@unique:unique()
) -> {ok, gleam@erlang@process:subject(sprocket@sprocket:message())} |
{error, nil}.
pop_sprocket(Ca, Ws) ->
gleam@erlang@process:call(
Ca,
fun(_capture) -> {pop_sprocket, _capture, Ws} end,
sprocket@internal@constants:call_timeout()
).
-spec get_cuid_channel(gleam@erlang@process:subject(message())) -> gleam@erlang@process:subject(ids@cuid:message()).
get_cuid_channel(Ca) ->
case get_state(Ca) of
{state, _, _, _, Cuid_channel} ->
Cuid_channel
end.
-spec validate_csrf(gleam@erlang@process:subject(message()), binary()) -> {ok,
nil} |
{error, nil}.
validate_csrf(Ca, Csrf) ->
case get_state(Ca) of
{state, _, _, Csrf_validator, _} ->
Csrf_validator(Csrf)
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(
<<"value"/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@internal@patch:patch(), boolean()) -> binary().
update_to_json(Update, Debug) ->
_pipe = gleam@json:preprocessed_array(
[gleam@json:string(<<"update"/utf8>>),
sprocket@internal@patch:patch_to_json(Update, Debug),
gleam@json:object([{<<"debug"/utf8>>, gleam@json:bool(Debug)}])]
),
gleam@json:to_string(_pipe).
-spec hook_event_to_json(binary(), binary(), gleam@option:option(binary())) -> binary().
hook_event_to_json(Id, Event, Value) ->
_pipe = gleam@json:preprocessed_array(
[gleam@json:string(<<"hook:event"/utf8>>), case Value of
{some, Value@1} ->
gleam@json:object(
[{<<"id"/utf8>>, gleam@json:string(Id)},
{<<"kind"/utf8>>, gleam@json:string(Event)},
{<<"value"/utf8>>, gleam@json:string(Value@1)}]
);
none ->
gleam@json:object(
[{<<"id"/utf8>>, gleam@json:string(Id)},
{<<"kind"/utf8>>, gleam@json:string(Event)}]
)
end]
),
gleam@json:to_string(_pipe).
-spec rendered_to_json(sprocket@render:rendered_element()) -> binary().
rendered_to_json(Update) ->
_pipe = gleam@json:preprocessed_array(
[gleam@json:string(<<"ok"/utf8>>),
(erlang:element(2, sprocket@internal@render@json:renderer()))(
Update
)]
),
gleam@json:to_string(_pipe).
-spec connect(
gleam@erlang@process:subject(message()),
sprocket@internal@utils@unique:unique(),
sprocket@context:element(),
fun((binary()) -> {ok, nil} | {error, nil})
) -> {ok, nil} | {error, nil}.
connect(Ca, Id, View, Ws_send) ->
Updater = {updater,
fun(Update) ->
_ = Ws_send(
update_to_json(Update, erlang:element(3, get_state(Ca)))
),
{ok, nil}
end},
Dispatcher = {dispatcher,
fun(Id@1, Event, Payload) ->
_ = Ws_send(hook_event_to_json(Id@1, Event, Payload)),
{ok, nil}
end},
Sprocket = sprocket@sprocket:start(
Id,
View,
get_cuid_channel(Ca),
{some, Updater},
{some, Dispatcher}
),
push_sprocket(Ca, Sprocket),
sprocket@internal@logger:info(
<<"Sprocket connected! "/utf8,
(sprocket@internal@utils@unique:to_string(Id))/binary>>
),
Rendered = sprocket@sprocket:render(Sprocket),
Ws_send(rendered_to_json(Rendered)).
-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 client_message(
gleam@erlang@process:subject(message()),
sprocket@internal@utils@unique:unique(),
sprocket@context:element(),
binary(),
fun((binary()) -> {ok, nil} | {error, nil})
) -> {ok, nil} | {error, nil}.
client_message(Ca, Id, View, Msg, Ws_send) ->
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}}} ->
sprocket@internal@logger:info(<<"New client joined"/utf8>>),
case validate_csrf(Ca, Csrf) of
{ok, _} ->
connect(Ca, Id, View, Ws_send);
{error, _} ->
sprocket@internal@logger:error(
<<"Invalid CSRF token"/utf8>>
),
Ws_send(error_to_json(invalid_csrf_token))
end;
{ok, {<<"event"/utf8>>, {event_payload, Kind, Event_id, Value}}} ->
sprocket@internal@logger:info(
<<<<<<"Event: "/utf8, Kind/binary>>/binary, " "/utf8>>/binary,
Event_id/binary>>
),
case get_sprocket(Ca, Id) of
{ok, Sprocket} ->
case sprocket@sprocket:get_handler(Sprocket, Event_id) of
{ok, {event_handler, _, Handler}} ->
_pipe = Handler(
gleam@option:map(
Value,
fun(Value@1) ->
sprocket@html@attributes:callback_param_from_string(
Value@1
)
end
)
),
{ok, _pipe};
_ ->
{ok, nil}
end;
_ ->
{error, nil}
end;
{ok,
{<<"hook:event"/utf8>>,
{hook_event_payload, Hook_id, Name, Value@2}}} ->
sprocket@internal@logger:info(
<<<<<<"Hook Event: "/utf8, Hook_id/binary>>/binary, " "/utf8>>/binary,
Name/binary>>
),
case get_sprocket(Ca, Id) of
{ok, Sprocket@1} ->
case sprocket@sprocket:get_client_hook(Sprocket@1, Hook_id) of
{ok, {client, _, _, Handle_event}} ->
Reply_dispatcher = fun(Event, Payload) ->
Ws_send(
hook_event_to_json(Hook_id, Event, Payload)
)
end,
gleam@option:map(
Handle_event,
fun(Handle_event@1) ->
Handle_event@1(
Name,
Value@2,
Reply_dispatcher
)
end
),
{ok, nil};
_ ->
sprocket@internal@logger:error(
<<"Error: no client hook defined for id: "/utf8,
Hook_id/binary>>
),
gleam@io:debug(Value@2),
erlang:error(#{gleam_error => panic,
message => <<"panic expression evaluated"/utf8>>,
module => <<"sprocket/cassette"/utf8>>,
function => <<"client_message"/utf8>>,
line => 249})
end;
_ ->
{error, nil}
end,
{ok, nil};
{error, E} ->
sprocket@internal@logger:error(<<"Error decoding message"/utf8>>),
gleam@io:debug(E),
{error, nil}
end.