Current section

Files

Jump to
slipstream lib slipstream connection impl.ex
Raw

lib/slipstream/connection/impl.ex

# we should use Application.compile_env/3 because it's available to us, but
# this is a library which may be used by projects with elixir versions <= 1.9.0
# and we really only use the application-config-to-module-attribute thing for
# testing. In addition, we really _want_ this to be a compile-time config.
# credo:disable-for-this-file Credo.Check.Warning.ApplicationConfigInModuleAttribute
defmodule Slipstream.Connection.Impl do
@moduledoc false
alias Slipstream.Connection.State
alias Slipstream.Message
alias Slipstream.Serializer
import Slipstream.Signatures, only: [event: 1]
@spec route_event(State.t(), event :: struct()) :: term()
def route_event(%State{client_pid: pid}, event) do
send(pid, event(event))
end
def http_connect(config) do
Mint.HTTP.connect(
map_http_scheme(config.uri.scheme),
config.uri.host,
config.uri.port,
config.mint_opts
)
end
# coveralls-ignore-start
defp map_http_scheme("wss"), do: :https
# coveralls-ignore-stop
defp map_http_scheme(_), do: :http
# coveralls-ignore-start
defp map_ws_scheme("wss"), do: :wss
# coveralls-ignore-stop
defp map_ws_scheme(_), do: :ws
def websocket_upgrade(conn, config) do
Mint.WebSocket.upgrade(
map_ws_scheme(config.uri.scheme),
conn,
path(config.uri),
config.headers,
extensions: config.extensions
)
end
# ---
def push_message(frame, state) when is_tuple(frame) do
case Mint.WebSocket.encode(state.websocket, frame) do
{:ok, websocket, data} ->
case Mint.WebSocket.stream_request_body(
state.conn,
state.request_ref,
data
) do
{:ok, conn} ->
{:ok, %State{state | conn: conn, websocket: websocket}}
# coveralls-ignore-start
{:error, conn, reason} ->
{:error, put_in(state.conn, conn), reason}
# coveralls-ignore-stop
end
# coveralls-ignore-start
{:error, websocket, reason} ->
{:error, put_in(state.websocket, websocket), reason}
# coveralls-ignore-stop
end
end
def push_message(%Message{payload: {:binary, _}} = message, state) do
push_message({:binary, encode(message, state)}, state)
end
def push_message(message, state) do
message =
case encode(message, state) do
{:binary, message} -> {:binary, message}
message when is_binary(message) -> {:text, message}
end
push_message(message, state)
end
# coveralls-ignore-start
def push_heartbeat(state) do
%Message{
topic: "phoenix",
event: "heartbeat",
ref: state.heartbeat_ref,
payload: %{}
}
|> push_message(state)
end
# coveralls-ignore-stop
defp encode(%Message{} = message, state) do
module = state.config.serializer
module.encode!(message, json_parser: state.config.json_parser)
end
def decode_message({encoding, message}, state)
when encoding in [:text, :binary] and is_binary(message) do
module = state.config.serializer
try do
module.decode!(message,
opcode: encoding,
json_parser: state.config.json_parser
)
rescue
# coveralls-ignore-start
_ in [Serializer.DecodeError] ->
message
# coveralls-ignore-stop
end
end
# coveralls-ignore-start
def decode_message(:ping, _state), do: :ping
def decode_message(:pong, _state), do: :pong
def decode_message({:close, _, _} = message, _state), do: message
# coveralls-ignore-stop
# this method of getting the path of a URI (including query) is maybe a bit
# unorthodox, but I think it's better than string manipulation
@spec path(URI.t()) :: String.t()
def path(%URI{} = uri) do
# select the v2 JSON serialization pattern
query = URI.decode_query(uri.query || "", %{"vsn" => "2.0.0"})
uri
|> Map.merge(%{
authority: nil,
host: nil,
port: nil,
scheme: nil,
userinfo: nil,
path: uri.path || "/",
query: URI.encode_query(query)
})
|> URI.to_string()
end
end