defmodule Wampex.Router.Transports.WebSocket do @moduledoc false @behaviour :cowboy_websocket require Logger alias __MODULE__ alias Wampex.Router alias Wampex.Router.Realms alias Wampex.Serializers.{JSON, MessagePack} @protocol_header "sec-websocket-protocol" @json "wamp.2.json" @msgpack "wamp.2.msgpack" defstruct [:serializer, :session, :last_message] def send_request(transport, message) do send(transport, {:send_request, message}) end @impl true def init(req, %{db: db, name: name, authentication_module: atm, authorization_module: azm}) do {:ok, serializer, protocol} = get_serializer(req) req = :cowboy_req.set_resp_header(@protocol_header, protocol, req) {:cowboy_websocket, req, {%WebSocket{serializer: serializer}, db, name, atm, azm}} end @impl true def websocket_init({state, db, name, atm, azm}) do {:ok, session} = Realms.start_session(Router.realms_name(name), db, name, WebSocket, self(), atm, azm) Process.link(session) Logger.debug("#{__MODULE__} Websocket Initialized: #{inspect(state)}") {:ok, %WebSocket{state | session: session}} end @impl true def websocket_handle({type, payload}, state) do Logger.debug("Received #{type} Payload: #{payload}") handle_payload({payload, state}) end @impl true def websocket_handle(:ping, state) do {[:pong], state} end @impl true def websocket_handle(unknown, state) do Logger.warning("#{__MODULE__} Unknown websocket frame #{inspect(unknown)}") {:ok, state} end @impl true def websocket_info({:send_request, message}, %WebSocket{serializer: s} = state) do {[{s.data_type(), s.serialize!(message)}], state} end @impl true def websocket_info( {:EXIT, _pid = session_pid, reason}, %WebSocket{session: session_pid, last_message: lm} = state ) do Logger.warning( "#{__MODULE__} Session ended because #{inspect(reason)}, closing connection. Last message #{inspect(lm)}" ) {:stop, state} end @impl true def websocket_info(event, state) do Logger.debug("#{__MODULE__} Received unhandled event: #{inspect(event)}") {:ok, state} end @impl true def terminate(reason, _req, _state) do Logger.debug("#{__MODULE__} Websocket closing for reason #{inspect(reason)}") :ok end defp handle_payload({payload, %WebSocket{serializer: s} = state}) do payload |> s.deserialize!() |> handle_event(state) end defp handle_event(ms, %WebSocket{session: s} = state) do send(s, {:set_message, ms}) {:ok, %WebSocket{state | last_message: ms}} end defp get_serializer(req) do @protocol_header |> :cowboy_req.parse_header(req) |> parse_protocol() end defp parse_protocol([]), do: Logger.error("Unknown protocol") defp parse_protocol([@json | _]), do: {:ok, JSON, @json} defp parse_protocol([@msgpack | _]), do: {:ok, MessagePack, @msgpack} defp parse_protocol([_ | t]), do: parse_protocol(t) end