Class: Tidewave::BrowserControl
- Inherits:
-
Object
- Object
- Tidewave::BrowserControl
- Defined in:
- lib/tidewave/browser_control.rb
Overview
Server side of Tidewave browser control.
The WebSocket endpoint is a dedicated Action Cable server, since the user app may not have one, and if it does it likely has auth. Commands/replies are routed between MCP request threads and browser connections over Action Cable pub/sub streams:
* tidewave:clients - all registered pages (used for discovery)
* tidewave:client:name - the page registered under name
* tidewave:reply:ref - replies to a single run_tool command
Consequently the routing works across processes whenever the configured cable adapter does, such as "solid_cable", whereas the default "async" adapter is single-process.
The pub/sub bus cannot tell whether a stream has any subscribers, so the channel broadcasts an "ack" on the reply stream as soon as it picks up a command, letting the caller fail fast when no client is connected. Similarly, when a page disconnects, its channel broadcasts "disconnected" for every command still awaiting a reply, so the caller does not wait out the full timeout.
Defined Under Namespace
Classes: Channel, ClientRegistry, Connection, Server
Constant Summary collapse
- CLIENTS_STREAM =
"tidewave:clients"
Instance Attribute Summary collapse
-
#server ⇒ Object
readonly
Returns the value of attribute server.
Class Method Summary collapse
Instance Method Summary collapse
-
#broadcast_run(tool_name, input, timeout_ms) ⇒ Object
Sends the tool to every connected client and returns the first reply.
-
#call(env) ⇒ Object
Rack entrypoint for the WebSocket endpoint.
-
#initialize(cable:, logger: nil, ack_timeout: 1.0) ⇒ BrowserControl
constructor
A new instance of BrowserControl.
-
#run(sid, tool_name, input, timeout_ms) ⇒ Object
Runs the tool against the client owning
sidand waits for the reply.
Constructor Details
#initialize(cable:, logger: nil, ack_timeout: 1.0) ⇒ BrowserControl
Returns a new instance of BrowserControl.
46 47 48 49 |
# File 'lib/tidewave/browser_control.rb', line 46 def initialize(cable:, logger: nil, ack_timeout: 1.0) @ack_timeout = ack_timeout @server = Server.new(cable: cable, logger: logger || ::Logger.new(IO::NULL)) end |
Instance Attribute Details
#server ⇒ Object (readonly)
Returns the value of attribute server.
44 45 46 |
# File 'lib/tidewave/browser_control.rb', line 44 def server @server end |
Class Method Details
.client_stream(name) ⇒ Object
36 37 38 |
# File 'lib/tidewave/browser_control.rb', line 36 def self.client_stream(name) "tidewave:client:#{name}" end |
.reply_stream(ref) ⇒ Object
40 41 42 |
# File 'lib/tidewave/browser_control.rb', line 40 def self.reply_stream(ref) "tidewave:reply:#{ref}" end |
Instance Method Details
#broadcast_run(tool_name, input, timeout_ms) ⇒ Object
Sends the tool to every connected client and returns the first reply.
Used for the discovery handshake (a browser_eval call with no sid).
Returns [ :ok, reply ] or [ :error, :timeout ] when no client
answered in time (the bus cannot tell whether anyone is connected).
73 74 75 |
# File 'lib/tidewave/browser_control.rb', line 73 def broadcast_run(tool_name, input, timeout_ms) call_tool(CLIENTS_STREAM, tool_name, nil, input, timeout_ms, await_ack: false) end |
#call(env) ⇒ Object
Rack entrypoint for the WebSocket endpoint.
52 53 54 |
# File 'lib/tidewave/browser_control.rb', line 52 def call(env) @server.call(env) end |
#run(sid, tool_name, input, timeout_ms) ⇒ Object
Runs the tool against the client owning sid and waits for the reply.
timeout_ms may be nil to wait indefinitely.
Returns [ :ok, reply ] (the page's response) or [ :error, reason ],
where reason is :invalid_sid, :unknown_client, :timeout, or :disconnected.
61 62 63 64 65 66 |
# File 'lib/tidewave/browser_control.rb', line 61 def run(sid, tool_name, input, timeout_ms) name = parse_sid(sid) return [ :error, :invalid_sid ] unless name call_tool(self.class.client_stream(name), tool_name, sid, input, timeout_ms, await_ack: true) end |