defmodule Driver8.Session do @moduledoc false require Logger use GenServer def start_link([registry, session_id, body_map] = _opts) do GenServer.start_link(__MODULE__, [session_id, body_map], name: registry_name(registry, session_id) ) end @doc """ """ def new(supervisor, registry, opts) do session_id = UUID.uuid1() result = DynamicSupervisor.start_child(supervisor, %{ id: session_id, start: {Driver8.Session, :start_link, [[registry, session_id, opts]]}, restart: :transient }) case result do {:ok, _} -> {:ok, session_id} _ -> {:error} end end @doc """ """ def find(registry, session_id) do case Registry.lookup(registry, session_id) do [{pid, _}] -> {:ok, pid} [] -> :no_session end end @doc """ Remove session with given id. """ def remove(registry, session_id) do case find(registry, session_id) do {:ok, pid} -> Logger.info("Deleting session: #{session_id}") GenServer.stop(pid) :no_session -> :no_session end end def reset_timer(registry, session_id) do case find(registry, session_id) do {:ok, pid} -> Logger.debug("Timer reset for session: #{session_id}") GenServer.cast(pid, :reset_timer) :no_session -> Logger.warn("Trying to reset timer on invalid session #{session_id}") end end @impl true def init([session_id, body_map]) do state = body_map |> Map.get("desiredCapabilities") |> (&%{ "sessionId" => session_id, "capabilities" => %{}, "desiredCapabilities" => &1 }).() Logger.info("Session starting: #{session_id}") {:ok, Map.put(state, :timeout, timeout(state))} end @impl true def handle_cast(:reset_timer, %{timeout: nil} = state) do {:noreply, state} end @impl true def handle_cast(:reset_timer, %{timeout: timer_ref} = state) do Process.cancel_timer(timer_ref) {:noreply, %{state | timeout: timeout(state)}} end @impl true def handle_info(:timeout, %{"sessionId" => session_id} = state) do Logger.info("Session is terminating (idle timeout): #{session_id}") {:stop, :normal, state} end @default_timeout 60 * 1000 defp timeout(%{"desiredCapabilities" => %{"sessionTimeout" => timeoutString}}) do timeout = duration(timeoutString) if timeout == 0 do Process.send_after(self(), :timeout, @default_timeout) else Process.send_after(self(), :timeout, timeout) end end defp timeout(_) do Process.send_after(self(), :timeout, @default_timeout) end defp duration(durationString) do captures = Regex.named_captures( ~r/((?\d+)h)?((?\d+)m)?((?\d+)s)?((?\d+)[mu]s)?/, durationString ) millis(captures["hours"], 60 * 60 * 1000) + millis(captures["minutes"], 60 * 1000) + millis(captures["seconds"], 1000) + millis(captures["millis"], 1) end defp millis(value, multiplier) do cond do is_nil(value) -> 0 String.length(value) === 0 -> 0 true -> String.to_integer(value) * multiplier end end defp registry_name(registry, session_id), do: {:via, Registry, {registry, session_id}} end