Packages

Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.

Current section

Files

Jump to
raxol_terminal lib raxol terminal session_manager helpers.ex
Raw

lib/raxol/terminal/session_manager/helpers.ex

defmodule Raxol.Terminal.SessionManager.Helpers do
@moduledoc """
Helper functions for SessionManager operations.
Provides utilities for session cleanup, timing, and other session management tasks.
"""
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
@default_cleanup_interval_ms Raxol.Core.Defaults.health_check_interval_ms()
@doc """
Starts a cleanup timer for periodic session maintenance.
## Parameters
- interval: Time interval in milliseconds for cleanup
## Returns
Timer reference
"""
@spec start_cleanup_timer(non_neg_integer()) :: reference()
def start_cleanup_timer(interval)
when is_integer(interval) and interval > 0 do
Process.send_after(self(), :cleanup_sessions, interval)
end
def start_cleanup_timer(_interval) do
# Default to 30 seconds if invalid interval provided
Process.send_after(self(), :cleanup_sessions, @default_cleanup_interval_ms)
end
@doc """
Cancels a cleanup timer.
## Parameters
- timer_ref: Timer reference to cancel
## Returns
- `:ok` if timer was canceled
- `{:error, :not_found}` if timer doesn't exist
"""
@spec cancel_cleanup_timer(reference() | nil) :: :ok | {:error, :not_found}
def cancel_cleanup_timer(nil), do: :ok
def cancel_cleanup_timer(timer_ref) when is_reference(timer_ref) do
case Process.cancel_timer(timer_ref) do
false -> {:error, :not_found}
_ -> :ok
end
end
@doc """
Checks if a session has expired based on last activity.
## Parameters
- last_activity: Timestamp of last activity
- timeout: Timeout duration in milliseconds
## Returns
Boolean indicating if session is expired
"""
@spec session_expired?(integer(), non_neg_integer()) :: boolean()
def session_expired?(last_activity, timeout)
when is_integer(last_activity) and is_integer(timeout) do
current_time = System.monotonic_time(:millisecond)
current_time - last_activity > timeout
end
def session_expired?(_last_activity, _timeout), do: false
@doc """
Generates a unique session ID.
## Returns
String session ID
"""
@spec generate_session_id() :: String.t()
def generate_session_id do
:crypto.strong_rand_bytes(16)
|> Base.encode64(padding: false)
|> String.replace(["+", "/"], fn
"+" -> "-"
"/" -> "_"
end)
end
@doc """
Validates session configuration.
## Parameters
- config: Session configuration map
## Returns
- `{:ok, config}` if valid
- `{:error, reason}` if invalid
"""
@spec validate_session_config(map()) :: {:ok, map()} | {:error, atom()}
def validate_session_config(config) when is_map(config) do
with :ok <- validate_dimensions(config),
:ok <- validate_timeout(config) do
{:ok, config}
end
end
def validate_session_config(_config), do: {:error, :invalid_config}
defp validate_dimensions(%{width: w, height: h})
when is_integer(w) and w > 0 and is_integer(h) and h > 0,
do: :ok
# Dimensions are optional - matches any map
defp validate_dimensions(%{}), do: :ok
defp validate_timeout(%{timeout: t}) when is_integer(t) and t > 0, do: :ok
# Timeout is optional - matches any map
defp validate_timeout(%{}), do: :ok
@doc """
Merges session options with defaults.
## Parameters
- opts: User-provided options
- defaults: Default options
## Returns
Merged options map
"""
@spec merge_session_options(Keyword.t() | map(), map()) :: map()
def merge_session_options(opts, defaults) when is_list(opts) do
opts
|> Enum.into(%{})
|> merge_session_options(defaults)
end
def merge_session_options(opts, defaults)
when is_map(opts) and is_map(defaults) do
Map.merge(defaults, opts)
end
def merge_session_options(_opts, defaults), do: defaults
@doc """
Formats session info for display.
## Parameters
- session: Session data map
## Returns
Formatted string
"""
@spec format_session_info(map()) :: String.t()
def format_session_info(%{id: id, created_at: created, active: active} = session) do
status = if active, do: "active", else: "inactive"
dimensions = get_dimensions_string(session)
"Session #{id} (#{status}) - Created: #{format_timestamp(created)}#{dimensions}"
end
def format_session_info(_), do: "Invalid session"
defp get_dimensions_string(%{width: w, height: h}), do: " - Size: #{w}x#{h}"
defp get_dimensions_string(_), do: ""
defp format_timestamp(timestamp) when is_integer(timestamp) do
timestamp
|> DateTime.from_unix!(:millisecond)
|> DateTime.to_string()
end
defp format_timestamp(_), do: "unknown"
@doc """
Creates a Client struct from an id, session_id, and config map.
"""
def create_client(client_id, session_id, config) do
alias Raxol.Terminal.SessionManager.Client
%Client{
id: client_id,
session_id: session_id,
connection_type: Map.get(config, :connection_type, :local),
connected_at: System.monotonic_time(:millisecond),
last_activity: System.monotonic_time(:millisecond),
terminal_size: Map.get(config, :terminal_size, {@default_width, @default_height}),
capabilities: Map.get(config, :capabilities, [:resize, :color, :mouse]),
metadata: Map.get(config, :metadata, %{})
}
end
@doc """
Generates a unique session ID based on name and timestamp.
"""
def generate_session_id(name) do
timestamp = System.unique_integer([:positive, :monotonic])
Base.encode16(:crypto.hash(:sha256, "#{name}-#{timestamp}"))
|> String.slice(0, 16)
end
@doc """
Generates a unique client ID.
"""
def generate_client_id do
"client_" <> Base.encode16(:crypto.strong_rand_bytes(4))
end
@doc """
Sends input to a terminal process if it is alive.
"""
def send_to_terminal(terminal_pid, input) do
if Process.alive?(terminal_pid) do
GenServer.call(terminal_pid, {:send_input, input})
else
{:error, :terminal_dead}
end
end
@doc """
Initializes the network server for session sharing.
"""
def init_network_server(false, _port), do: nil
def init_network_server(true, port) do
alias Raxol.Core.Runtime.Log
Log.info("Session sharing server started on port #{port}")
%{port: port, enabled: true}
end
end