Class: TUI::CableClient

Inherits:
Object
  • Object
show all
Defined in:
lib/tui/cable_client.rb

Overview

Action Cable WebSocket client for connecting the TUI to the brain server. Runs the WebSocket connection in a background thread and exposes a thread-safe message queue for the TUI render loop to drain.

Implements the actioncable-v1-json protocol: subscribe to a SessionChannel, receive event broadcasts, and send user input via the speak action.

Automatically reconnects with exponential backoff when the connection drops unexpectedly. Detects stale connections via Action Cable ping heartbeat monitoring.

Examples:

client = TUI::CableClient.new(host: "localhost:42134", session_id: 1)
client.connect
client.speak("Hello!")
messages = client.drain_messages
client.disconnect

Constant Summary collapse

DISCONNECT_TIMEOUT =

seconds to wait for WebSocket thread to finish

2
POLL_INTERVAL =

seconds between connection status checks

0.1
CONNECTION_TIMEOUT =

seconds to wait for the connecting state to advance

10
MAX_RECONNECT_ATTEMPTS =
10
BACKOFF_BASE =

initial backoff delay in seconds

1.0
BACKOFF_CAP =

maximum backoff delay

30.0
PING_STALE_THRESHOLD =

seconds without ping before connection is stale

6.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, session_id:) ⇒ CableClient

Returns a new instance of CableClient.

Parameters:

  • host (String)

    brain server address (e.g. “localhost:42134”)

  • session_id (Integer)

    session to subscribe to



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/tui/cable_client.rb', line 48

def initialize(host:, session_id:)
  @host = host
  @session_id = session_id
  @status = :disconnected
  @message_queue = Thread::Queue.new
  @mutex = Mutex.new
  @ws = nil
  @ws_thread = nil
  @intentional_disconnect = false
  @reconnect_attempt = 0
  @last_ping_at = nil
  @connection_generation = 0
end

Instance Attribute Details

#hostString (readonly)

Returns brain server host:port.

Returns:

  • (String)

    brain server host:port



35
36
37
# File 'lib/tui/cable_client.rb', line 35

def host
  @host
end

#reconnect_attemptInteger (readonly)

Returns current reconnection attempt (0 when connected).

Returns:

  • (Integer)

    current reconnection attempt (0 when connected)



44
45
46
# File 'lib/tui/cable_client.rb', line 44

def reconnect_attempt
  @reconnect_attempt
end

#session_idInteger (readonly)

Returns current session ID.

Returns:

  • (Integer)

    current session ID



38
39
40
# File 'lib/tui/cable_client.rb', line 38

def session_id
  @session_id
end

#statusSymbol (readonly)

Returns connection status (:disconnected, :connecting, :connected, :subscribed, :reconnecting).

Returns:

  • (Symbol)

    connection status (:disconnected, :connecting, :connected, :subscribed, :reconnecting)



41
42
43
# File 'lib/tui/cable_client.rb', line 41

def status
  @status
end

Instance Method Details

#change_view_mode(mode) ⇒ void

This method returns an undefined value.

Requests the brain to change the session’s view mode. The server broadcasts view_mode_changed to all clients on the session, followed by the re-decorated viewport.

Parameters:

  • mode (String)

    one of “basic”, “verbose”, “debug”



109
110
111
# File 'lib/tui/cable_client.rb', line 109

def change_view_mode(mode)
  send_action("change_view_mode", {"view_mode" => mode})
end

#connectObject

Opens the WebSocket connection in a background thread. The connection subscribes to the session channel automatically after receiving the Action Cable welcome message. Reconnects automatically on unexpected disconnection.



66
67
68
69
70
71
72
# File 'lib/tui/cable_client.rb', line 66

def connect
  @mutex.synchronize do
    @intentional_disconnect = false
    @status = :connecting
  end
  @ws_thread = Thread.new { run_websocket_loop }
end

#create_sessionObject

Requests the brain to create a new session and switch to it. The server responds with a session_changed message followed by history.



83
84
85
# File 'lib/tui/cable_client.rb', line 83

def create_session
  send_action("create_session", {})
end

#disconnectObject

Closes the WebSocket connection and cleans up the background thread. Prevents automatic reconnection.



147
148
149
150
151
152
153
154
# File 'lib/tui/cable_client.rb', line 147

def disconnect
  @mutex.synchronize do
    @intentional_disconnect = true
    @status = :disconnected
  end
  @ws&.close
  @ws_thread&.join(DISCONNECT_TIMEOUT)
end

#drain_messagesArray<Hash>

Drains all pending messages from the queue (non-blocking). Call this from the TUI render loop to process incoming events.

Returns:

  • (Array<Hash>)

    messages received since last drain



124
125
126
127
128
129
130
131
132
# File 'lib/tui/cable_client.rb', line 124

def drain_messages
  messages = []
  loop do
    messages << @message_queue.pop(true)
  rescue ThreadError
    break
  end
  messages
end

#list_sessions(limit: 10) ⇒ Object

Requests a list of recent sessions from the brain. The server responds with a sessions_list message.

Parameters:

  • limit (Integer) (defaults to: 10)

    max sessions to return (default 10)



99
100
101
# File 'lib/tui/cable_client.rb', line 99

def list_sessions(limit: 10)
  send_action("list_sessions", {"limit" => limit})
end

#resubscribe(new_session_id) ⇒ Object

Deprecated.

Use #create_session or #switch_session instead. The server now handles stream switching via the session protocol.

Unsubscribes from the current session and subscribes to a new one.

Parameters:

  • new_session_id (Integer)

    session to switch to



139
140
141
142
143
# File 'lib/tui/cable_client.rb', line 139

def resubscribe(new_session_id)
  unsubscribe_current
  @mutex.synchronize { @session_id = new_session_id }
  subscribe
end

#speak(content) ⇒ Object

Sends user input to the brain for processing.

Parameters:

  • content (String)

    the user’s message text



77
78
79
# File 'lib/tui/cable_client.rb', line 77

def speak(content)
  send_action("speak", {"content" => content})
end

#switch_session(session_id) ⇒ Object

Requests the brain to switch to an existing session. The server responds with a session_changed message followed by history.

Parameters:

  • session_id (Integer)

    target session to resume



91
92
93
# File 'lib/tui/cable_client.rb', line 91

def switch_session(session_id)
  send_action("switch_session", {"session_id" => session_id})
end

#update_session_id(new_id) ⇒ Object

Updates the local session ID reference after a server-side session switch.

Parameters:

  • new_id (Integer)

    the new session ID



116
117
118
# File 'lib/tui/cable_client.rb', line 116

def update_session_id(new_id)
  @mutex.synchronize { @session_id = new_id }
end