Show a dynamically updated list of NTS servers #1

Merged
mike merged 21 commits from liveview into main 2024-07-26 09:35:19 -04:00
7 changed files with 93 additions and 10 deletions
Showing only changes of commit 92251c4084 - Show all commits

View File

@ -23,6 +23,7 @@ defmodule Chronoscope.Application do
{Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]}, {Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]},
# Start to serve requests, typically the last entry # Start to serve requests, typically the last entry
ChronoscopeWeb.Endpoint, ChronoscopeWeb.Endpoint,
# Initialize clients for the main LiveView
Chronoscope.ViewActivator Chronoscope.ViewActivator
] ]

View File

@ -33,6 +33,7 @@ defmodule Chronoscope.ViewActivator do
@nts_file @nts_file
|> File.stream!() |> File.stream!()
|> Stream.map(&String.trim/1) |> Stream.map(&String.trim/1)
|> Stream.filter(&(&1 != ""))
|> Stream.map(&Parse.parse_nts_server/1) |> Stream.map(&Parse.parse_nts_server/1)
|> Enum.to_list() |> Enum.to_list()
|> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end) |> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end)

View File

@ -13,7 +13,7 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
def get(conn, %{"host" => host, "port" => port, "path" => path}) do def get(conn, %{"host" => host, "port" => port, "path" => path}) do
try do try do
handle_get(conn, %{host: host, port: String.to_integer(port), path: path}) handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: path})
rescue rescue
ArgumentError -> bad_request_response(conn, "invalid port") ArgumentError -> bad_request_response(conn, "invalid port")
end end
@ -25,20 +25,24 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
def get(conn, %{"host" => host, "port" => port}) do def get(conn, %{"host" => host, "port" => port}) do
try do try do
handle_get(conn, %{host: host, port: String.to_integer(port), path: @default_path}) handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: @default_path})
rescue rescue
ArgumentError -> bad_request_response(conn, "invalid port") ArgumentError -> bad_request_response(conn, "invalid port")
end end
end end
def get(conn, %{"host" => host}) do def get(conn, %{"host" => host}) do
handle_get(conn, %{host: host, port: @default_port, path: @default_path}) handle_get(conn, %{host: String.trim(host), port: @default_port, path: @default_path})
end end
def get(conn, _params) do def get(conn, _params) do
bad_request_response(conn, "missing host") bad_request_response(conn, "missing host")
end end
defp handle_get(conn, %{host: ""}) do
bad_request_response(conn, "empty host")
end
defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do
case connect(host, port, path) do case connect(host, port, path) do
{:ok, response} -> {:ok, response} ->

View File

@ -12,20 +12,24 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
def get(conn, %{"host" => host, "port" => port}) do def get(conn, %{"host" => host, "port" => port}) do
try do try do
handle_get(conn, %{host: host, port: String.to_integer(port)}) handle_get(conn, %{host: String.trim(host), port: String.to_integer(port)})
rescue rescue
ArgumentError -> bad_request_response(conn, "invalid port") ArgumentError -> bad_request_response(conn, "invalid port")
end end
end end
def get(conn, %{"host" => host}) do def get(conn, %{"host" => host}) do
handle_get(conn, %{host: host, port: @default_port}) handle_get(conn, %{host: String.trim(host), port: @default_port})
end end
def get(conn, _params) do def get(conn, _params) do
bad_request_response(conn, "missing host") bad_request_response(conn, "missing host")
end end
defp handle_get(conn, %{host: ""}) do
bad_request_response(conn, "empty host")
end
defp handle_get(conn, %{host: host, port: port}) when port > 0 and port < 65536 do defp handle_get(conn, %{host: host, port: port}) when port > 0 and port < 65536 do
case key_establishment_response(host, port) do case key_establishment_response(host, port) do
{:ok, response} -> {:ok, response} ->

View File

@ -3,26 +3,33 @@ defmodule ChronoscopeWeb.IndexLive do
alias Chronoscope.NTS alias Chronoscope.NTS
alias Chronoscope.NTS.KeyEstablishmentResponse alias Chronoscope.NTS.KeyEstablishmentResponse
alias Chronoscope.ViewActivator
@topic Application.compile_env(:chronoscope, :nts_topic) @topic Application.compile_env(:chronoscope, :nts_topic)
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
ChronoscopeWeb.Endpoint.subscribe(@topic) ChronoscopeWeb.Endpoint.subscribe(@topic)
{:ok, assign(socket, %{clients: client_list()})} {:ok, assign(socket, %{servers: server_list(), clients: client_list()})}
end end
def handle_info(%{topic: @topic, event: "key-exchange", payload: client}, socket) do def handle_info(%{topic: @topic, event: "key-exchange", payload: client}, socket) do
if client.server in socket.assigns.servers do
{:noreply, update(socket, :clients, &update_client(&1, client))} {:noreply, update(socket, :clients, &update_client(&1, client))}
else
{:noreply, socket}
end
end end
defp update_client(client_list, client) do defp update_client(client_list, client) do
# todo - use a map instead of list for fast lookups?
Enum.map(client_list, &if(client.server == &1.server, do: client, else: &1)) Enum.map(client_list, &if(client.server == &1.server, do: client, else: &1))
end end
defp server_list() do
GenServer.call(ViewActivator, :get_nts_servers)
end
defp client_list() do defp client_list() do
Chronoscope.ViewActivator server_list()
|> GenServer.call(:get_nts_servers)
|> NTS.list_clients() |> NTS.list_clients()
end end
end end

View File

@ -17,6 +17,42 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionControllerTest do
assert %{"error" => "missing host"} == response assert %{"error" => "missing host"} == response
end end
test "requires a host name value", %{conn: conn} do
response1 =
conn
|> get(~p"/api/v1/gemini/connect?host=")
|> json_response(400)
response2 =
conn
|> get(~p"/api/v1/gemini/connect?host=&port=1966")
|> json_response(400)
assert %{"error" => "empty host"} == response1
assert %{"error" => "empty host"} == response2
end
test "requires a non-blank host name", %{conn: conn} do
response1 =
conn
|> get(~p"/api/v1/gemini/connect?host=%20%20")
|> json_response(400)
response2 =
conn
|> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966")
|> json_response(400)
response3 =
conn
|> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966&path=/test")
|> json_response(400)
assert %{"error" => "empty host"} == response1
assert %{"error" => "empty host"} == response2
assert %{"error" => "empty host"} == response3
end
test "uses the given host name", %{conn: conn} do test "uses the given host name", %{conn: conn} do
GeminiMock GeminiMock
|> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end) |> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end)

View File

@ -17,6 +17,36 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
assert %{"error" => "missing host"} == response assert %{"error" => "missing host"} == response
end end
test "requires a host name value", %{conn: conn} do
response1 =
conn
|> get(~p"/api/v1/nts/key-establishment?host=")
|> json_response(400)
response2 =
conn
|> get(~p"/api/v1/nts/key-establishment?host=&port=4444")
|> json_response(400)
assert %{"error" => "empty host"} == response1
assert %{"error" => "empty host"} == response2
end
test "requires a non-blank host name", %{conn: conn} do
response1 =
conn
|> get(~p"/api/v1/nts/key-establishment?host=%20%20")
|> json_response(400)
response2 =
conn
|> get(~p"/api/v1/nts/key-establishment?host=%20%20&port=4444")
|> json_response(400)
assert %{"error" => "empty host"} == response1
assert %{"error" => "empty host"} == response2
end
test "uses the given host name", %{conn: conn} do test "uses the given host name", %{conn: conn} do
NTSMock NTSMock
|> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end) |> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)