-module(mist@websocket). -compile(no_auto_import). -export([frame_from_message/1, frame_to_bit_builder/1, upgrade/2, to_text_frame/1, send/2, crypto_hash/2, base64_encode/1, parse_key/1, upgrade_socket/1, echo_handler/2, with_handler/1, on_init/2, on_close/2]). -export_type([message/0, frame/0, sha_hash/0, websocket_handler/0]). -type message() :: {text_message, binary()}. -type frame() :: {close_frame, integer(), binary()} | {text_frame, integer(), binary()} | {ping_frame, integer(), binary()} | {pong_frame, integer(), binary()}. -type sha_hash() :: sha. -type websocket_handler() :: {websocket_handler, gleam@option:option(fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)), gleam@option:option(fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)), fun((message(), gleam@otp@process:sender(glisten@tcp:handler_message())) -> {ok, nil} | {error, nil})}. -spec unmask_data(bitstring(), list(bitstring()), integer(), bitstring()) -> bitstring(). unmask_data(Data, Masks, Index, Resp) -> case Data of <<>> -> Resp; <> -> {ok, Mask_value@1} = case gleam@list:at(Masks, Index rem 4) of {ok, Mask_value} -> {ok, Mask_value}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/websocket"/utf8>>, function => <<"unmask_data"/utf8>>, line => 44}) end, Unmasked = crypto:exor(Mask_value@1, Masked), unmask_data( Rest, Masks, Index + 1, <> ) end. -spec frame_from_message(bitstring()) -> {ok, frame()} | {error, nil}. frame_from_message(Message) -> <<_@3:1, _@4:3, Opcode@1:4/integer, 1:1, Payload_length@1:7/integer, Mask1@1:8/bitstring, Mask2@1:8/bitstring, Mask3@1:8/bitstring, Mask4@1:8/bitstring, Rest@1/bitstring>> = case Message of <<_@1:1, _@2:3, Opcode:4/integer, 1:1, Payload_length:7/integer, Mask1:8/bitstring, Mask2:8/bitstring, Mask3:8/bitstring, Mask4:8/bitstring, Rest/bitstring>> -> <<_@1:1, _@2:3, Opcode:4/integer, 1:1, Payload_length:7/integer, Mask1:8/bitstring, Mask2:8/bitstring, Mask3:8/bitstring, Mask4:8/bitstring, Rest/bitstring>>; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 57}) end, case begin _pipe = Rest@1, _pipe@1 = unmask_data( _pipe, [Mask1@1, Mask2@1, Mask3@1, Mask4@1], 0, <<>> ), gleam@bit_string:to_string(_pipe@1) end of {error, _try@1} -> {error, _try@1}; {ok, Data} -> _pipe@2 = case Opcode@1 of 1 -> {text_frame, Payload_length@1, Data}; 8 -> {close_frame, Payload_length@1, Data} end, {ok, _pipe@2} end. -spec frame_to_bit_builder(frame()) -> gleam@bit_builder:bit_builder(). frame_to_bit_builder(Frame) -> case Frame of {text_frame, Payload_length, Payload} -> Fin = 1, Mask_flag = 0, Payload_bs = gleam@bit_string:from_string(Payload), _pipe = <>, gleam@bit_builder:from_bit_string(_pipe); {close_frame, Payload_length, Payload} -> Fin = 1, Mask_flag = 0, Payload_bs = gleam@bit_string:from_string(Payload), _pipe = <>, gleam@bit_builder:from_bit_string(_pipe); {ping_frame, _@1, _@2} -> gleam@bit_builder:from_bit_string(<<>>); {pong_frame, Payload_length@1, Payload@1} -> Payload_bs = gleam@bit_string:from_string(Payload@1), _pipe = <<1:1, 0:3, 10:4, 0:1, Payload_length@1:7, Payload_bs/bitstring>>, gleam@bit_builder:from_bit_string(_pipe) end. -spec upgrade(glisten@tcp:socket(), gleam@http@request:request(bitstring())) -> {ok, nil} | {error, nil}. upgrade(Socket, Req) -> case begin _pipe = upgrade_socket(Req), gleam@result:replace_error(_pipe, nil) end of {error, _try} -> {error, _try}; {ok, Resp} -> case begin _pipe@1 = Resp, _pipe@2 = mist@encoder:to_bit_builder(_pipe@1), _pipe@3 = glisten@tcp:send(Socket, _pipe@2), gleam@result:replace_error(_pipe@3, nil) end of {error, _try@1} -> {error, _try@1}; {ok, _@1} -> {ok, nil} end end. -spec to_text_frame(binary()) -> gleam@bit_builder:bit_builder(). to_text_frame(Data) -> Size = begin _pipe = Data, _pipe@1 = gleam@bit_string:from_string(_pipe), gleam@bit_string:byte_size(_pipe@1) end, frame_to_bit_builder({text_frame, Size, Data}). -spec send(glisten@tcp:socket(), binary()) -> {ok, nil} | {error, glisten@tcp:socket_reason()}. send(Socket, Data) -> _pipe = Data, _pipe@1 = to_text_frame(_pipe), glisten@tcp:send(Socket, _pipe@1). -spec crypto_hash(sha_hash(), binary()) -> binary(). crypto_hash(A, B) -> crypto:hash(A, B). -spec base64_encode(binary()) -> binary(). base64_encode(A) -> base64:encode(A). -spec parse_key(binary()) -> binary(). parse_key(Key) -> _pipe = Key, _pipe@1 = gleam@string:append( _pipe, <<"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"/utf8>> ), _pipe@2 = crypto:hash(sha, _pipe@1), base64:encode(_pipe@2). -spec upgrade_socket(gleam@http@request:request(bitstring())) -> {ok, gleam@http@response:response(gleam@bit_builder:bit_builder())} | {error, gleam@http@request:request(bitstring())}. upgrade_socket(Req) -> case begin _pipe = gleam@http@request:get_header(Req, <<"upgrade"/utf8>>), gleam@result:replace_error(_pipe, Req) end of {error, _try} -> {error, _try}; {ok, _@1} -> case begin _pipe@1 = gleam@http@request:get_header( Req, <<"sec-websocket-key"/utf8>> ), gleam@result:replace_error(_pipe@1, Req) end of {error, _try@1} -> {error, _try@1}; {ok, Key} -> case begin _pipe@2 = gleam@http@request:get_header( Req, <<"sec-websocket-version"/utf8>> ), gleam@result:replace_error(_pipe@2, Req) end of {error, _try@2} -> {error, _try@2}; {ok, _@2} -> Accept_key = parse_key(Key), _pipe@3 = gleam@http@response:new(101), _pipe@4 = gleam@http@response:set_body( _pipe@3, gleam@bit_builder:from_bit_string(<<""/utf8>>) ), _pipe@5 = gleam@http@response:prepend_header( _pipe@4, <<"Upgrade"/utf8>>, <<"websocket"/utf8>> ), _pipe@6 = gleam@http@response:prepend_header( _pipe@5, <<"Connection"/utf8>>, <<"Upgrade"/utf8>> ), _pipe@7 = gleam@http@response:prepend_header( _pipe@6, <<"Sec-WebSocket-Accept"/utf8>>, Accept_key ), {ok, _pipe@7} end end end. -spec echo_handler( message(), gleam@otp@process:sender(glisten@tcp:handler_message()) ) -> {ok, nil} | {error, nil}. echo_handler(Msg, Sender) -> _@1 = gleam@otp@process:send( Sender, {send_message, to_text_frame(erlang:element(2, Msg))} ), {ok, nil}. -spec with_handler( fun((message(), gleam@otp@process:sender(glisten@tcp:handler_message())) -> {ok, nil} | {error, nil}) ) -> websocket_handler(). with_handler(Func) -> {websocket_handler, none, none, Func}. -spec on_init( websocket_handler(), fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil) ) -> websocket_handler(). on_init(Handler, Func) -> erlang:setelement(3, Handler, {some, Func}). -spec on_close( websocket_handler(), fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil) ) -> websocket_handler(). on_close(Handler, Func) -> erlang:setelement(2, Handler, {some, Func}).