-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, 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 the `type` field 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()} | {status_msg, binary()} | {error_msg, binary(), binary()} | {raw_msg, binary()}. -type ws_command() :: {subscribe, list(glupbit@websocket@subscription:subscription()), glupbit@websocket@subscription:ws_format()}. -type ws_state(ACEP) :: {ws_state, fun((ACEP, ws_message()) -> ACEP), ACEP}. -file("src/glupbit/websocket/connection.gleam", 76). ?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", 146). -spec status_decoder() -> gleam@dynamic@decode:decoder(binary()). status_decoder() -> gleam@dynamic@decode:field( <<"status"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Status) -> gleam@dynamic@decode:success(Status) end ). -file("src/glupbit/websocket/connection.gleam", 151). -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", 157). -spec type_field_decoder() -> gleam@dynamic@decode:decoder(binary()). type_field_decoder() -> gleam@dynamic@decode:field( <<"type"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Type_name) -> gleam@dynamic@decode:success(Type_name) end ). -file("src/glupbit/websocket/connection.gleam", 116). -spec decode_ws_message(binary()) -> ws_message(). decode_ws_message(Text) -> case gleam@json:parse(Text, status_decoder()) of {ok, Status} -> {status_msg, Status}; {error, _} -> case gleam@json:parse(Text, error_decoder()) of {ok, {Name, Message}} -> {error_msg, Name, Message}; {error, _} -> case gleam@json:parse(Text, type_field_decoder()) of {ok, <<"ticker"/utf8>>} -> case gleam@json:parse( Text, glupbit@websocket@subscription:ticker_data_decoder( ) ) of {ok, Data} -> {ticker_msg, Data}; {error, _} -> {raw_msg, Text} end; {ok, <<"trade"/utf8>>} -> case gleam@json:parse( Text, glupbit@websocket@subscription:trade_data_decoder( ) ) of {ok, Data@1} -> {trade_msg, Data@1}; {error, _} -> {raw_msg, Text} end; {ok, <<"orderbook"/utf8>>} -> case gleam@json:parse( Text, glupbit@websocket@subscription:orderbook_data_decoder( ) ) of {ok, Data@2} -> {orderbook_msg, Data@2}; {error, _} -> {raw_msg, Text} end; {ok, _} -> {raw_msg, Text}; {error, _} -> {raw_msg, Text} end end end. -file("src/glupbit/websocket/connection.gleam", 86). -spec start_ws( gleam@http@request:request(binary()), ACFA, fun((ACFA, ws_message()) -> ACFA) ) -> {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, {subscribe, Subs, Format}} -> Ticket = youid@uuid:v4_string(), Msg_text = glupbit@websocket@subscription:build_subscription_message( Ticket, Subs, Format ), _ = 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", 55). ?DOC(" Connect to the public WebSocket.\n"). -spec connect_public(ACES, fun((ACES, ws_message()) -> ACES)) -> {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 => 59, value => _assert_fail, start => 1500, 'end' => 1546, pattern_start => 1511, pattern_end => 1518}) end, start_ws(Req@1, User_state, Handler). -file("src/glupbit/websocket/connection.gleam", 64). ?DOC(" Connect to the private WebSocket with authentication.\n"). -spec connect_private( glupbit@auth:credentials(), ACEV, fun((ACEV, ws_message()) -> ACEV) ) -> {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 => 69, value => _assert_fail, start => 1858, 'end' => 1905, pattern_start => 1869, pattern_end => 1876}) 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 => 70, value => _assert_fail@1, start => 1908, 'end' => 1957, pattern_start => 1919, pattern_end => 1928}) end, Req@2 = gleam@http@request:set_header( Req@1, <<"authorization"/utf8>>, <<"Bearer "/utf8, Token@1/binary>> ), start_ws(Req@2, User_state, Handler).