Class: Ferrum::Client::WebSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/ferrum/client/web_socket.rb

Constant Summary collapse

WEBSOCKET_BUG_SLEEP =
0.05
DEFAULT_PORTS =
{ "ws" => 80, "wss" => 443 }.freeze
SKIP_LOGGING_SCREENSHOTS =
!ENV["FERRUM_LOGGING_SCREENSHOTS"]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, max_receive_size, logger) ⇒ WebSocket

Returns a new instance of WebSocket.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ferrum/client/web_socket.rb', line 16

def initialize(url, max_receive_size, logger)
  @url    = url
  @logger = logger
  uri     = URI.parse(@url)
  port    = uri.port || DEFAULT_PORTS[uri.scheme]

  if port == 443
    tcp = TCPSocket.new(uri.host, port)
    ssl_context = OpenSSL::SSL::SSLContext.new
    @sock = OpenSSL::SSL::SSLSocket.new(tcp, ssl_context)
    @sock.sync_close = true
    @sock.connect
  else
    @sock = TCPSocket.new(uri.host, port)
  end

  max_receive_size ||= ::WebSocket::Driver::MAX_LENGTH
  @driver   = ::WebSocket::Driver.client(self, max_length: max_receive_size)
  @messages = Queue.new

  @screenshot_commands = Concurrent::Hash.new if SKIP_LOGGING_SCREENSHOTS

  @driver.on(:open,    &method(:on_open))
  @driver.on(:message, &method(:on_message))
  @driver.on(:close,   &method(:on_close))

  start

  @driver.start
end

Instance Attribute Details

#messagesObject (readonly)

Returns the value of attribute messages.



14
15
16
# File 'lib/ferrum/client/web_socket.rb', line 14

def messages
  @messages
end

#urlObject (readonly)

Returns the value of attribute url.



14
15
16
# File 'lib/ferrum/client/web_socket.rb', line 14

def url
  @url
end

Instance Method Details

#closeObject



89
90
91
# File 'lib/ferrum/client/web_socket.rb', line 89

def close
  @driver.close
end

#on_close(_event) ⇒ Object



69
70
71
72
73
# File 'lib/ferrum/client/web_socket.rb', line 69

def on_close(_event)
  @messages.close
  @sock.close
  @thread.kill
end

#on_message(event) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ferrum/client/web_socket.rb', line 52

def on_message(event)
  data = safely_parse_json(event.data)
  # If we couldn't parse JSON data for some reason (parse error or deeply nested object) we
  # don't push response to @messages. Worse that could happen we raise timeout error due to command didn't return
  # anything or skip the background notification, but at least we don't crash the thread that crashes the main
  # thread and the application.
  @messages.push(data) if data

  output = event.data
  if SKIP_LOGGING_SCREENSHOTS && @screenshot_commands[data&.dig("id")]
    @screenshot_commands.delete(data&.dig("id"))
    output.sub!(/{"data":"[^"]*"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
  end

  @logger&.puts("#{Utils::ElapsedTime.elapsed_time} #{output}\n")
end

#on_open(_event) ⇒ Object



47
48
49
50
# File 'lib/ferrum/client/web_socket.rb', line 47

def on_open(_event)
  # https://github.com/faye/websocket-driver-ruby/issues/46
  sleep(WEBSOCKET_BUG_SLEEP)
end

#send_message(data) ⇒ Object



75
76
77
78
79
80
81
# File 'lib/ferrum/client/web_socket.rb', line 75

def send_message(data)
  @screenshot_commands[data[:id]] = true if SKIP_LOGGING_SCREENSHOTS

  json = data.to_json
  @driver.text(json)
  @logger&.puts("\n\n▶ #{Utils::ElapsedTime.elapsed_time} #{json}")
end

#write(data) ⇒ Object



83
84
85
86
87
# File 'lib/ferrum/client/web_socket.rb', line 83

def write(data)
  @sock.write(data)
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, IOError # rubocop:disable Lint/ShadowedException
  @messages.close
end