Class: Ferrum::Client

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/ferrum/client.rb,
lib/ferrum/client/subscriber.rb,
lib/ferrum/client/web_socket.rb

Overview

The low-level CDP client. Owns the WebSocket connection to the browser, assigns command ids, matches responses back to their pending commands and dispatches incoming events to the Subscriber. SessionClient builds on top of it to scope commands/events to a particular target's session.

Defined Under Namespace

Classes: Subscriber, WebSocket

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ws_url, options) ⇒ Client

Returns a new instance of Client.



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/ferrum/client.rb', line 157

def initialize(ws_url, options)
  @command_id = 0
  @command_id_mutex = Mutex.new
  @ws_url = ws_url
  @options = options
  @pendings = Concurrent::Hash.new
  @ws = WebSocket.new(ws_url, options.ws_max_receive_size, options.logger)
  @subscriber = Subscriber.new

  start
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



155
156
157
# File 'lib/ferrum/client.rb', line 155

def options
  @options
end

#subscriberObject (readonly)

Returns the value of attribute subscriber.



155
156
157
# File 'lib/ferrum/client.rb', line 155

def subscriber
  @subscriber
end

#ws_urlObject (readonly)

Returns the value of attribute ws_url.



155
156
157
# File 'lib/ferrum/client.rb', line 155

def ws_url
  @ws_url
end

Instance Method Details

#build_message(method, params) ⇒ Hash

Builds a CDP message hash with a fresh, thread-safe command id.

Parameters:

  • method (String)

    The CDP method name.

  • params (Hash)

    The command's parameters.

Returns:

  • (Hash)


325
326
327
# File 'lib/ferrum/client.rb', line 325

def build_message(method, params)
  { method: method, params: params }.merge(id: next_command_id)
end

#closevoid

This method returns an undefined value.

Closes the underlying websocket, drops pending commands and stops the message-processing thread and subscriber.



294
295
296
297
298
299
300
# File 'lib/ferrum/client.rb', line 294

def close
  @ws.close
  # Give a thread some time to handle a tail of messages
  @pendings.clear
  @thread.kill unless @thread.join(1)
  @subscriber.close
end

#command(method, async: false, timeout: nil, **params) ⇒ Boolean, Hash

Sends a CDP command to the browser-wide session.

Parameters:

  • method (String)

    The CDP method name, e.g. "Target.createTarget".

  • async (Boolean) (defaults to: false)

    Whether to send the command without waiting for a response.

  • params (Hash)

    The command's parameters.

  • timeout (Numeric, nil) (defaults to: nil)

    How long to wait for this command's response, overriding Browser::Options#protocol_timeout. See #send_message.

Returns:

  • (Boolean, Hash)

    true when sent asynchronously, otherwise the command's result.



188
189
190
191
# File 'lib/ferrum/client.rb', line 188

def command(method, async: false, timeout: nil, **params)
  message = build_message(method, params)
  send_message(message, async: async, timeout: timeout)
end

#inspectString

Custom inspection that exposes internal state useful for debugging.

Returns:

  • (String)


307
308
309
310
311
312
# File 'lib/ferrum/client.rb', line 307

def inspect
  "#<#{self.class} " \
    "@command_id=#{@command_id.inspect} " \
    "@pendings=#{@pendings.inspect} " \
    "@ws=#{@ws.inspect}>"
end

#off(event, id) ⇒ void

This method returns an undefined value.

Unsubscribes from a CDP event.

Parameters:

  • event (String)

    The CDP event name.

  • id (Integer)

    The subscription id returned by #on.



261
262
263
# File 'lib/ferrum/client.rb', line 261

def off(event, id)
  @subscriber.off(event, id)
end

#on(event) ⇒ Integer

Subscribes to a CDP event.

Parameters:

  • event (String)

    The CDP event name.

Returns:

  • (Integer)

    The subscription id, used to unsubscribe via #off.



246
247
248
# File 'lib/ferrum/client.rb', line 246

def on(event, &)
  @subscriber.on(event, &)
end

#send_message(message, async:, timeout: nil) ⇒ Boolean, Hash

Sends a raw CDP message over the websocket. Synchronous calls block until a matching response arrives, or timeout elapses, defaulting to protocol_timeout (delegated to Browser::Options#protocol_timeout). That default is the transport-level budget for internal CDP bookkeeping (e.g. Target.createTarget). Page#command overrides this back to timeout, or a caller-supplied budget (e.g. #pdf/ #screenshot's own timeout: argument), for the user-facing commands it issues -- some of which (e.g. Page.navigate, Page.printToPDF) rely on their own response latency to detect a stuck operation.

Parameters:

  • message (Hash)

    The message to send, must include an :id key.

  • async (Boolean)

    Whether to return immediately instead of waiting for a response.

  • timeout (Numeric, nil) (defaults to: nil)

    How long to wait for the response. Defaults to protocol_timeout.

Returns:

  • (Boolean, Hash)

    true when sent asynchronously, otherwise the parsed "result" from the response.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/ferrum/client.rb', line 217

def send_message(message, async:, timeout: nil)
  if async
    @ws.send_message(message)
    true
  else
    pending = Concurrent::IVar.new
    @pendings[message[:id]] = pending
    @ws.send_message(message)
    data = pending.value!(timeout || protocol_timeout)
    @pendings.delete(message[:id])

    raise DeadBrowserError if data.nil? && @ws.messages.closed?
    raise TimeoutError unless data

    error, response = data.values_at("error", "result")
    raise_browser_error(error) if error
    response
  end
end

#session(session_id) ⇒ SessionClient

Builds a client scoped to a given CDP session, e.g. a browsing context created via Target.attachToTarget.

Parameters:

  • session_id (String)

    The CDP session id to scope commands and events to.

Returns:



284
285
286
# File 'lib/ferrum/client.rb', line 284

def session(session_id)
  SessionClient.new(self, session_id)
end

#subscribed?(event) ⇒ Boolean

Whether there's at least one callback registered for the event.

Parameters:

  • event (String)

    The CDP event name.

Returns:

  • (Boolean)


271
272
273
# File 'lib/ferrum/client.rb', line 271

def subscribed?(event)
  @subscriber.subscribed?(event)
end