-module(glupbit@websocket@connection). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glupbit/websocket/connection.gleam"). -export([subscribe/3, list_subscriptions/1, connect_public/2, connect_private/3]). -export_type([ws_message/0, ws_command/0, ws_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. ?MODULEDOC( " WebSocket connection management via stratus actors.\n" "\n" " Handles public and private (authenticated) connections with automatic\n" " message routing based on `type` / `method` fields in incoming JSON.\n" ). -type ws_message() :: {ticker_msg, glupbit@websocket@subscription:ticker_data()} | {trade_msg, glupbit@websocket@subscription:trade_data()} | {orderbook_msg, glupbit@websocket@subscription:orderbook_data()} | {subscription_list_msg, glupbit@websocket@subscription:list_subscriptions_response()} | {status_msg, binary()} | {error_msg, binary(), binary()} | {raw_msg, binary()}. -type ws_command() :: {subscribe, list(glupbit@websocket@subscription:subscription()), glupbit@websocket@subscription:ws_format()} | list_subscriptions. -type ws_state(ACLY) :: {ws_state, fun((ACLY, ws_message()) -> ACLY), ACLY}. -file("src/glupbit/websocket/connection.gleam", 78). ?DOC(" Send a subscription request over the WebSocket.\n"). -spec subscribe( gleam@erlang@process:subject(stratus:internal_message(ws_command())), list(glupbit@websocket@subscription:subscription()), glupbit@websocket@subscription:ws_format() ) -> nil. subscribe(Conn, Subscriptions, Format) -> gleam@erlang@process:send( Conn, stratus:to_user_message({subscribe, Subscriptions, Format}) ). -file("src/glupbit/websocket/connection.gleam", 87). ?DOC(" Query the list of active subscriptions on the WebSocket.\n"). -spec list_subscriptions( gleam@erlang@process:subject(stratus:internal_message(ws_command())) ) -> nil. list_subscriptions(Conn) -> gleam@erlang@process:send(Conn, stratus:to_user_message(list_subscriptions)). -file("src/glupbit/websocket/connection.gleam", 121). -spec build_command_message(ws_command()) -> binary(). build_command_message(Cmd) -> Ticket = youid@uuid:v4_string(), case Cmd of {subscribe, Subs, Format} -> glupbit@websocket@subscription:build_subscription_message( Ticket, Subs, Format ); list_subscriptions -> glupbit@websocket@subscription:build_list_subscriptions_message( Ticket ) end. -file("src/glupbit/websocket/connection.gleam", 170). -spec try_decode( binary(), gleam@dynamic@decode:decoder(ACMM), fun((ACMM) -> ws_message()) ) -> ws_message(). try_decode(Text, Decoder, To_msg) -> case gleam@json:parse(Text, Decoder) of {ok, Data} -> To_msg(Data); {error, _} -> {raw_msg, Text} end. -file("src/glupbit/websocket/connection.gleam", 160). -spec decode_by_type(binary(), binary()) -> ws_message(). decode_by_type(Text, Type_name) -> case Type_name of <<"ticker"/utf8>> -> try_decode( Text, glupbit@websocket@subscription:ticker_data_decoder(), fun(Field@0) -> {ticker_msg, Field@0} end ); <<"trade"/utf8>> -> try_decode( Text, glupbit@websocket@subscription:trade_data_decoder(), fun(Field@0) -> {trade_msg, Field@0} end ); <<"orderbook"/utf8>> -> try_decode( Text, glupbit@websocket@subscription:orderbook_data_decoder(), fun(Field@0) -> {orderbook_msg, Field@0} end ); _ -> {raw_msg, Text} end. -file("src/glupbit/websocket/connection.gleam", 181). -spec field_decoder(binary()) -> gleam@dynamic@decode:decoder(binary()). field_decoder(Name) -> gleam@dynamic@decode:field( Name, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Value) -> gleam@dynamic@decode:success(Value) end ). -file("src/glupbit/websocket/connection.gleam", 143). -spec decode_data_message(binary()) -> ws_message(). decode_data_message(Text) -> case gleam@json:parse(Text, field_decoder(<<"method"/utf8>>)) of {ok, <<"LIST_SUBSCRIPTIONS"/utf8>>} -> try_decode( Text, glupbit@websocket@subscription:list_subscriptions_response_decoder( ), fun(Field@0) -> {subscription_list_msg, Field@0} end ); {ok, _} -> {raw_msg, Text}; {error, _} -> case gleam@json:parse(Text, field_decoder(<<"type"/utf8>>)) of {ok, Type_name} -> decode_by_type(Text, Type_name); {error, _} -> {raw_msg, Text} end end. -file("src/glupbit/websocket/connection.gleam", 186). -spec error_decoder() -> gleam@dynamic@decode:decoder({binary(), binary()}). error_decoder() -> gleam@dynamic@decode:subfield( [<<"error"/utf8>>, <<"name"/utf8>>], {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Name) -> gleam@dynamic@decode:subfield( [<<"error"/utf8>>, <<"message"/utf8>>], {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Message) -> gleam@dynamic@decode:success({Name, Message}) end ) end ). -file("src/glupbit/websocket/connection.gleam", 132). -spec decode_ws_message(binary()) -> ws_message(). decode_ws_message(Text) -> case gleam@json:parse(Text, field_decoder(<<"status"/utf8>>)) of {ok, Status} -> {status_msg, Status}; {error, _} -> case gleam@json:parse(Text, error_decoder()) of {ok, {Name, Message}} -> {error_msg, Name, Message}; {error, _} -> decode_data_message(Text) end end. -file("src/glupbit/websocket/connection.gleam", 93). -spec start_ws( gleam@http@request:request(binary()), ACMJ, fun((ACMJ, ws_message()) -> ACMJ) ) -> {ok, gleam@erlang@process:subject(stratus:internal_message(ws_command()))} | {error, stratus:initialization_error()}. start_ws(Req, User_state, Handler) -> Initial_state = {ws_state, Handler, User_state}, _pipe = stratus:new(Req, Initial_state), _pipe@1 = stratus:on_message(_pipe, fun(State, Msg, Conn) -> case Msg of {text, Text} -> Ws_msg = decode_ws_message(Text), New_user_state = (erlang:element(2, State))( erlang:element(3, State), Ws_msg ), stratus:continue( {ws_state, erlang:element(2, State), New_user_state} ); {binary, _} -> stratus:continue(State); {user, Cmd} -> Msg_text = build_command_message(Cmd), _ = stratus:send_text_message(Conn, Msg_text), stratus:continue(State) end end), _pipe@2 = stratus:on_close(_pipe@1, fun(_, _) -> nil end), _pipe@3 = stratus:start(_pipe@2), gleam@result:map(_pipe@3, fun(Started) -> erlang:element(3, Started) end). -file("src/glupbit/websocket/connection.gleam", 57). ?DOC(" Connect to the public WebSocket.\n"). -spec connect_public(ACMB, fun((ACMB, ws_message()) -> ACMB)) -> {ok, gleam@erlang@process:subject(stratus:internal_message(ws_command()))} | {error, stratus:initialization_error()}. connect_public(User_state, Handler) -> Req@1 = case gleam@http@request:to( <<"wss://api.upbit.com/websocket/v1"/utf8>> ) of {ok, Req} -> Req; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"glupbit/websocket/connection"/utf8>>, function => <<"connect_public"/utf8>>, line => 61, value => _assert_fail, start => 1590, 'end' => 1636, pattern_start => 1601, pattern_end => 1608}) end, start_ws(Req@1, User_state, Handler). -file("src/glupbit/websocket/connection.gleam", 66). ?DOC(" Connect to the private WebSocket with authentication.\n"). -spec connect_private( glupbit@auth:credentials(), ACME, fun((ACME, ws_message()) -> ACME) ) -> {ok, gleam@erlang@process:subject(stratus:internal_message(ws_command()))} | {error, stratus:initialization_error()}. connect_private(Creds, User_state, Handler) -> Req@1 = case gleam@http@request:to( <<"wss://api.upbit.com/websocket/v1/private"/utf8>> ) of {ok, Req} -> Req; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"glupbit/websocket/connection"/utf8>>, function => <<"connect_private"/utf8>>, line => 71, value => _assert_fail, start => 1948, 'end' => 1995, pattern_start => 1959, pattern_end => 1966}) end, Token@1 = case glupbit@auth:generate_token(Creds) of {ok, Token} -> Token; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"glupbit/websocket/connection"/utf8>>, function => <<"connect_private"/utf8>>, line => 72, value => _assert_fail@1, start => 1998, 'end' => 2047, pattern_start => 2009, pattern_end => 2018}) end, Req@2 = gleam@http@request:set_header( Req@1, <<"authorization"/utf8>>, <<"Bearer "/utf8, Token@1/binary>> ), start_ws(Req@2, User_state, Handler).