chromate-rb 0.0.1.pre → 0.0.2.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/CHANGELOG.md +54 -3
  4. data/README.md +33 -6
  5. data/Rakefile +48 -16
  6. data/docker_root/Gemfile +4 -0
  7. data/docker_root/Gemfile.lock +28 -0
  8. data/docker_root/TestInDocker.gif +0 -0
  9. data/docker_root/app.rb +92 -0
  10. data/dockerfiles/Dockerfile +21 -7
  11. data/dockerfiles/README.md +49 -0
  12. data/docs/README.md +74 -0
  13. data/docs/browser.md +149 -92
  14. data/docs/element.md +289 -0
  15. data/lib/bot_browser/downloader.rb +52 -0
  16. data/lib/bot_browser/installer.rb +81 -0
  17. data/lib/bot_browser.rb +39 -0
  18. data/lib/chromate/actions/dom.rb +28 -9
  19. data/lib/chromate/actions/navigate.rb +4 -5
  20. data/lib/chromate/actions/screenshot.rb +30 -11
  21. data/lib/chromate/actions/stealth.rb +47 -0
  22. data/lib/chromate/browser.rb +64 -12
  23. data/lib/chromate/c_logger.rb +7 -0
  24. data/lib/chromate/client.rb +40 -18
  25. data/lib/chromate/configuration.rb +31 -14
  26. data/lib/chromate/element.rb +65 -15
  27. data/lib/chromate/elements/select.rb +59 -7
  28. data/lib/chromate/hardwares/keyboard_controller.rb +34 -0
  29. data/lib/chromate/hardwares/keyboards/virtual_controller.rb +65 -0
  30. data/lib/chromate/hardwares/mouse_controller.rb +47 -11
  31. data/lib/chromate/hardwares/mouses/linux_controller.rb +124 -21
  32. data/lib/chromate/hardwares/mouses/mac_os_controller.rb +6 -6
  33. data/lib/chromate/hardwares/mouses/virtual_controller.rb +95 -7
  34. data/lib/chromate/hardwares/mouses/x11.rb +36 -0
  35. data/lib/chromate/hardwares.rb +16 -0
  36. data/lib/chromate/helpers.rb +22 -15
  37. data/lib/chromate/user_agent.rb +39 -15
  38. data/lib/chromate/version.rb +1 -1
  39. data/lib/chromate.rb +2 -0
  40. data/logo.png +0 -0
  41. data/results/bot.png +0 -0
  42. data/results/brotector.png +0 -0
  43. data/results/cloudflare.png +0 -0
  44. data/results/headers.png +0 -0
  45. data/results/pixelscan.png +0 -0
  46. metadata +20 -2
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'chromate/helpers'
4
+ require 'chromate/c_logger'
5
+ require 'bot_browser/downloader'
6
+
7
+ module BotBrowser
8
+ class Installer
9
+ class << self
10
+ include Chromate::Helpers
11
+
12
+ def install(version = nil)
13
+ create_config_dir
14
+ binary_path, profile_path = Downloader.download(version)
15
+ bot_browser_path = install_binary(binary_path)
16
+ bot_browser_profile_path = install_profile(profile_path)
17
+
18
+ write_config(bot_browser_path, bot_browser_profile_path)
19
+ end
20
+
21
+ def config_dir
22
+ "#{Dir.home}/.botbrowser"
23
+ end
24
+
25
+ private
26
+
27
+ def install_binary(binary_path)
28
+ Chromate::CLogger.log("Installing binary from #{binary_path}")
29
+ return install_binary_mac(binary_path) if mac?
30
+ return install_binary_linux(binary_path) if linux?
31
+ return install_binary_windows(binary_path) if windows?
32
+
33
+ raise 'Unsupported platform'
34
+ end
35
+
36
+ def create_config_dir
37
+ Chromate::CLogger.log("Creating config directory at #{config_dir}")
38
+ `mkdir -p #{config_dir}`
39
+ end
40
+
41
+ def install_profile(profile_path)
42
+ Chromate::CLogger.log("Installing profile from #{profile_path}")
43
+ `cp #{profile_path} #{config_dir}/`
44
+
45
+ "#{config_dir}/#{File.basename(profile_path)}"
46
+ end
47
+
48
+ def install_binary_mac(binary_path)
49
+ `hdiutil attach #{binary_path}`
50
+ `cp -r /Volumes/Chromium/Chromium.app /Applications/`
51
+ `hdiutil detach /Volumes/Chromium`
52
+ `xattr -rd com.apple.quarantine /Applications/Chromium.app`
53
+ `codesign --force --deep --sign - /Applications/Chromium.app`
54
+
55
+ '/Applications/Chromium.app/Contents/MacOS/Chromium'
56
+ end
57
+
58
+ def install_binary_linux(binary_path)
59
+ `sudo dpkg -i #{binary_path}`
60
+ `sudo apt-get install -f`
61
+
62
+ '/usr/bin/chromium'
63
+ end
64
+
65
+ def install_binary_windows(binary_path)
66
+ `7z x #{binary_path}`
67
+
68
+ 'chromium.exe'
69
+ end
70
+
71
+ def write_config(bot_browser_path, bot_browser_profile_path)
72
+ Chromate::CLogger.log("Writing config to #{config_dir}/config.yml")
73
+ File.write(File.expand_path("#{config_dir}/config.yml"), <<~YAML)
74
+ ---
75
+ bot_browser_path: #{bot_browser_path}
76
+ profile: #{bot_browser_profile_path}
77
+ YAML
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'chromate/c_logger'
5
+ require 'bot_browser/installer'
6
+
7
+ module BotBrowser
8
+ class << self
9
+ def install(version = nil)
10
+ Installer.install(version)
11
+ end
12
+
13
+ def installed?
14
+ File.exist?("#{Dir.home}/.botbrowser/config.yml")
15
+ end
16
+
17
+ def load
18
+ yaml = YAML.load_file("#{Dir.home}/.botbrowser/config.yml")
19
+
20
+ Chromate.configure do |config|
21
+ ENV['CHROME_BIN'] = yaml['bot_browser_path']
22
+ config.args = [
23
+ "--bot-profile=#{yaml["profile"]}",
24
+ '--no-sandbox'
25
+ ]
26
+ config.startup_patch = false
27
+ end
28
+
29
+ Chromate::CLogger.log('BotBrowser loaded', level: :debug)
30
+ end
31
+ end
32
+ end
33
+
34
+ # Usage
35
+ # require 'bot_browser'
36
+
37
+ # BotBrowser.install
38
+ # BotBrowser.load
39
+ # browser = Chromate::Browser.new
@@ -3,36 +3,55 @@
3
3
  module Chromate
4
4
  module Actions
5
5
  module Dom
6
+ # @return [String]
7
+ def source
8
+ evaluate_script('document.documentElement.outerHTML')
9
+ end
10
+
11
+ # @param selector [String] CSS selector
12
+ # @return [Chromate::Element]
6
13
  def find_element(selector)
7
14
  Chromate::Element.new(selector, @client)
8
15
  end
9
16
 
17
+ # @param selector [String] CSS selector
18
+ # @return [Chromate::Element]
10
19
  def click_element(selector)
11
20
  find_element(selector).click
12
21
  end
13
22
 
23
+ # @param selector [String] CSS selector
24
+ # @return [Chromate::Element]
14
25
  def hover_element(selector)
15
26
  find_element(selector).hover
16
27
  end
17
28
 
29
+ # @param selector [String] CSS selector
30
+ # @param text [String] Text to type
31
+ # @return [Chromate::Element]
18
32
  def type_text(selector, text)
19
33
  find_element(selector).type(text)
20
34
  end
21
35
 
22
- def get_text(selector)
23
- find_element(selector).text
24
- end
25
-
26
- def get_property(selector, property)
27
- find_element(selector).attributes[property]
28
- end
29
-
36
+ # @param selector [String] CSS selector
37
+ # @return [String]
30
38
  def select_option(selector, option)
31
39
  Chromate::Elements::Select.new(selector, @client).select_option(option)
32
40
  end
33
41
 
42
+ # @param selector [String] CSS selector
43
+ # @return [String]
34
44
  def evaluate_script(script)
35
- @client.send_message('Runtime.evaluate', expression: script)
45
+ result = @client.send_message('Runtime.evaluate', expression: script)
46
+
47
+ case result['result']['type']
48
+ when 'string', 'number', 'boolean'
49
+ result['result']['value']
50
+ when 'object'
51
+ result['result']['objectId']
52
+ else
53
+ result['result']
54
+ end
36
55
  end
37
56
  end
38
57
  end
@@ -17,11 +17,11 @@ module Chromate
17
17
  dom_content_loaded = false
18
18
  frame_stopped_loading = false
19
19
 
20
- # Utiliser un Mutex pour la synchronisation
20
+ # Use Mutex for synchronization
21
21
  mutex = Mutex.new
22
22
  condition = ConditionVariable.new
23
23
 
24
- # S'abonner aux messages WebSocket
24
+ # Subscribe to websocket messages
25
25
  listener = proc do |message|
26
26
  mutex.synchronize do
27
27
  case message['method']
@@ -40,15 +40,14 @@ module Chromate
40
40
 
41
41
  @client.on_message(&listener)
42
42
 
43
- # Attendre les trois événements (DOMContent, Load et FrameStoppedLoading) avec un timeout
43
+ # Wait for all three events (DOMContent, Load and FrameStoppedLoading) with a timeout
44
44
  Timeout.timeout(15) do
45
45
  mutex.synchronize do
46
46
  condition.wait(mutex) until dom_content_loaded && page_loaded && frame_stopped_loading
47
47
  end
48
48
  end
49
49
 
50
- # Nettoyer l'écouteur WebSocket
51
- @client.on_message { |msg| } # Supprime tous les anciens écouteurs en ajoutant un listener vide
50
+ @client.on_message { |msg| } # Remove listener
52
51
 
53
52
  self
54
53
  end
@@ -3,12 +3,34 @@
3
3
  module Chromate
4
4
  module Actions
5
5
  module Screenshot
6
- def screenshot_to_file(file_path, options = {})
7
- image_data = screenshot(options)
6
+ # @param file_path [String] The path to save the screenshot to
7
+ # @param options [Hash] Options for the screenshot
8
+ # @option options [String] :format The format of the screenshot
9
+ # @option options [Boolean] :full_page Whether to take a screenshot of the full page
10
+ # @option options [Boolean] :fromSurface Whether to take a screenshot from the surface
11
+ # @return [Boolean] Whether the screenshot was successful
12
+ def screenshot(file_path = "#{Time.now.to_i}.png", options = {})
13
+ return xvfb_screenshot(file_path) if @xfvb
14
+ return screenshot_full_page(file_path, options) if options.delete(:full_page)
15
+
16
+ image_data = make_screenshot(options)
8
17
  File.binwrite(file_path, image_data)
9
18
  true
10
19
  end
11
20
 
21
+ private
22
+
23
+ # @param file_path [String] The path to save the screenshot to
24
+ # @return [Boolean] Whether the screenshot was successful
25
+ def xvfb_screenshot(file_path)
26
+ display = ENV['DISPLAY'] || ':99'
27
+ system("xwd -root -display #{display} | convert xwd:- #{file_path}")
28
+ end
29
+
30
+ # @param file_path [String] The path to save the screenshot to
31
+ # @param options [Hash] Options for the screenshot
32
+ # @option options [String] :format The format of the screenshot
33
+ # @return [Boolean] Whether the screenshot was successful
12
34
  def screenshot_full_page(file_path, options = {})
13
35
  metrics = @client.send_message('Page.getLayoutMetrics')
14
36
 
@@ -23,20 +45,17 @@ module Chromate
23
45
  deviceScaleFactor: 1
24
46
  })
25
47
 
26
- screenshot_to_file(file_path, options)
48
+ screenshot(file_path, options)
27
49
 
28
50
  @client.send_message('Emulation.clearDeviceMetricsOverride')
29
51
  true
30
52
  end
31
53
 
32
- def xvfb_screenshot(file_path)
33
- display = ENV['DISPLAY'] || ':99'
34
- system("xwd -root -display #{display} | convert xwd:- #{file_path}")
35
- end
36
-
37
- private
38
-
39
- def screenshot(options = {})
54
+ # @param options [Hash] Options for the screenshot
55
+ # @option options [String] :format The format of the screenshot
56
+ # @option options [Boolean] :fromSurface Whether to take a screenshot from the surface
57
+ # @return [String] The image data
58
+ def make_screenshot(options = {})
40
59
  default_options = {
41
60
  format: 'png',
42
61
  fromSurface: true
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chromate
4
+ module Actions
5
+ module Stealth
6
+ # @return [void]
7
+ def patch # rubocop:disable Metrics/MethodLength
8
+ @client.send_message('Network.enable')
9
+
10
+ # Define custom headers
11
+ custom_headers = {
12
+ 'User-Agent' => UserAgent.call,
13
+ 'Accept-Language' => 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,und;q=0.6,es;q=0.5,pt;q=0.4',
14
+ 'Sec-CH-UA' => '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
15
+ 'Sec-CH-UA-Platform' => '"' + UserAgent.os + '"', # rubocop:disable Style/StringConcatenation
16
+ 'Sec-CH-UA-Mobile' => '?0'
17
+ }
18
+
19
+ # Apply custom headers
20
+ @client.send_message('Network.setExtraHTTPHeaders', headers: custom_headers)
21
+
22
+ # Override User-Agent and high-entropy data to avoid fingerprinting
23
+ user_agent_override = {
24
+ userAgent: UserAgent.call,
25
+ platform: UserAgent.os,
26
+ acceptLanguage: 'fr-FR,fr;q=0.9,en-US;q=0.8',
27
+ userAgentMetadata: {
28
+ brands: [
29
+ { brand: 'Google Chrome', version: '131' },
30
+ { brand: 'Chromium', version: '131' },
31
+ { brand: 'Not_A Brand', version: '24' }
32
+ ],
33
+ fullVersion: '131.0.0.0',
34
+ platform: UserAgent.os,
35
+ platformVersion: UserAgent.os_version,
36
+ architecture: 'x86_64',
37
+ model: '',
38
+ mobile: false
39
+ }
40
+ }
41
+
42
+ # Apply User-Agent override and high-entropy data
43
+ @client.send_message('Network.setUserAgentOverride', user_agent_override)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -14,6 +14,7 @@ require_relative 'user_agent'
14
14
  require_relative 'actions/navigate'
15
15
  require_relative 'actions/screenshot'
16
16
  require_relative 'actions/dom'
17
+ require_relative 'actions/stealth'
17
18
 
18
19
  module Chromate
19
20
  class Browser
@@ -23,7 +24,15 @@ module Chromate
23
24
  include Actions::Navigate
24
25
  include Actions::Screenshot
25
26
  include Actions::Dom
26
-
27
+ include Actions::Stealth
28
+
29
+ # @param options [Hash] Options for the browser
30
+ # @option options [String] :chrome_path The path to the Chrome executable
31
+ # @option options [String] :user_data_dir The path to the user data directory
32
+ # @option options [Boolean] :headless Whether to run Chrome in headless mode
33
+ # @option options [Boolean] :xfvb Whether to run Chrome in Xvfb
34
+ # @option options [Boolean] :native_control Whether to use native controls
35
+ # @option options [Boolean] :record Whether to record the screen
27
36
  def initialize(options = {})
28
37
  @options = config.options.merge(options)
29
38
  @chrome_path = @options.fetch(:chrome_path)
@@ -38,8 +47,7 @@ module Chromate
38
47
  @client = nil
39
48
  @args = [
40
49
  @chrome_path,
41
- "--user-data-dir=#{@user_data_dir}",
42
- "--lang=#{@options[:lang] || "fr-FR"}"
50
+ "--user-data-dir=#{@user_data_dir}"
43
51
  ]
44
52
 
45
53
  trap('INT') { stop_and_exit }
@@ -48,13 +56,12 @@ module Chromate
48
56
  at_exit { stop }
49
57
  end
50
58
 
59
+ # @return [self]
51
60
  def start
52
61
  build_args
53
62
  @client = Client.new(self)
54
63
  @args << "--remote-debugging-port=#{@client.port}"
55
64
 
56
- start_video_recording if @record
57
-
58
65
  if @xfvb
59
66
  if ENV['DISPLAY'].nil?
60
67
  ENV['DISPLAY'] = ':0' if mac? # XQuartz generally uses :0 on Mac
@@ -63,49 +70,94 @@ module Chromate
63
70
  @args << "--display=#{ENV.fetch("DISPLAY", nil)}"
64
71
  end
65
72
 
73
+ Hardwares::MouseController.reset_mouse_position
74
+ Chromate::CLogger.log("Starting browser with args: #{@args}", level: :debug)
66
75
  @process = spawn(*@args, err: 'chrome_errors.log', out: 'chrome_output.log')
67
76
  sleep 2
68
77
 
69
78
  @client.start
79
+
80
+ start_video_recording if @record
81
+
82
+ patch if config.patch?
83
+
70
84
  self
71
85
  end
72
86
 
87
+ # @return [self]
73
88
  def stop
74
- Process.kill('TERM', @process) if @process
75
- Process.kill('TERM', @record_process) if @record_process
76
- Process.kill('TERM', @xfvb_process) if @xfvb_process
89
+ stop_process(@process) if @process
90
+ stop_process(@record_process) if @record_process
91
+ stop_process(@xfvb_process) if @xfvb_process
77
92
  @client&.stop
93
+
94
+ self
78
95
  end
79
96
 
97
+ # @return [Boolean]
80
98
  def native_control?
81
99
  @native_control
82
100
  end
83
101
 
84
102
  private
85
103
 
104
+ # @return [Integer]
86
105
  def start_video_recording
87
- outfile = File.join(Dir.pwd, "output_video_#{Time.now.to_i}.mp4")
88
- @record_process = spawn("ffmpeg -f x11grab -r 25 -s 1920x1080 -i #{ENV.fetch("DISPLAY", ":99")} -pix_fmt yuv420p -y #{outfile}")
106
+ outname = @record.is_a?(String) ? @record : "output_video_#{Time.now.to_i}.mp4"
107
+ outfile = File.join(Dir.pwd, outname)
108
+ # TODO: get screen resolution dynamically
109
+ @record_process = spawn(
110
+ "ffmpeg -f x11grab -draw_mouse 1 -r 30 -s 1920x1080 -i #{ENV.fetch("DISPLAY")} -c:v libx264 -preset ultrafast -pix_fmt yuv420p -y #{outfile}"
111
+ )
89
112
  end
90
113
 
114
+ # @return [Array<String>]
91
115
  def build_args
92
116
  exclude_switches = config.exclude_switches || []
93
117
  exclude_switches += @options[:exclude_switches] if @options[:exclude_switches]
94
118
 
119
+ if @options.dig(:options, :args)
120
+ @args += @options[:options][:args]
121
+ @args << "--exclude-switches=#{exclude_switches.join(",")}" if exclude_switches.any?
122
+ return @args
123
+ end
95
124
  @args += config.generate_arguments(**@options)
96
- @args += @options[:options][:args] if @options.dig(:options, :args)
97
125
  @args << "--user-agent=#{@options[:user_agent] || UserAgent.call}"
98
126
  @args << "--exclude-switches=#{exclude_switches.join(",")}" if exclude_switches.any?
99
127
 
100
128
  @args
101
129
  end
102
130
 
131
+ # @param pid [Integer] PID of the process to stop
132
+ # @param timeout [Integer] Timeout in seconds to wait for the process to stop
133
+ # @return [void]
134
+ def stop_process(pid, timeout: 5)
135
+ return unless pid
136
+
137
+ # Send SIGINT to the process to stop it gracefully
138
+ Process.kill('INT', pid)
139
+ begin
140
+ Timeout.timeout(timeout) do
141
+ Process.wait(pid)
142
+ end
143
+ rescue Timeout::Error
144
+ # If the process does not stop gracefully, send SIGKILL
145
+ CLogger.log("Process #{pid} did not stop gracefully. Sending SIGKILL...", level: :debug)
146
+ Process.kill('KILL', pid)
147
+ Process.wait(pid)
148
+ end
149
+ rescue Errno::ESRCH
150
+ # The process has already stopped
151
+ end
152
+
153
+ # @return [void]
103
154
  def stop_and_exit
104
- puts 'Stopping browser...'
155
+ CLogger.log('Stopping browser...', level: :debug)
105
156
  stop
106
157
  exit
107
158
  end
108
159
 
160
+ # @return [Chromate::Configuration]
109
161
  def config
110
162
  Chromate.configuration
111
163
  end
@@ -4,6 +4,9 @@ require 'logger'
4
4
 
5
5
  module Chromate
6
6
  class CLogger < Logger
7
+ # @param [IO] logdev
8
+ # @param [Integer] shift_age
9
+ # @param [Integer] shift_size
7
10
  def initialize(logdev, shift_age: 0, shift_size: 1_048_576)
8
11
  super(logdev, shift_age, shift_size)
9
12
  self.formatter = proc do |severity, datetime, _progname, msg|
@@ -11,10 +14,14 @@ module Chromate
11
14
  end
12
15
  end
13
16
 
17
+ # @return [Chromate::CLogger]
14
18
  def self.logger
15
19
  @logger ||= new($stdout)
16
20
  end
17
21
 
22
+ # @param [String] message
23
+ # @param [Symbol] level
24
+ # @return [void]
18
25
  def self.log(message, level: :info)
19
26
  logger.send(level, message)
20
27
  end
@@ -7,18 +7,21 @@ module Chromate
7
7
  class Client
8
8
  include Helpers
9
9
 
10
+ # @return [Array<Proc>]
10
11
  def self.listeners
11
12
  @@listeners ||= [] # rubocop:disable Style/ClassVars
12
13
  end
13
14
 
14
15
  attr_reader :port, :ws, :browser
15
16
 
17
+ # @param [Chromate::Browser] browser
16
18
  def initialize(browser)
17
- @browser = browser
18
- options = browser.options
19
- @port = options[:port] || find_available_port
19
+ @browser = browser
20
+ options = browser.options
21
+ @port = options[:port] || find_available_port
20
22
  end
21
23
 
24
+ # @return [self]
22
25
  def start
23
26
  @ws_url = fetch_websocket_debug_url
24
27
  @ws = WebSocket::Client::Simple.connect(@ws_url)
@@ -29,7 +32,7 @@ module Chromate
29
32
 
30
33
  @ws.on :message do |msg|
31
34
  message = JSON.parse(msg.data)
32
- client_self.handle_message(message)
35
+ client_self.send(:handle_message, message)
33
36
 
34
37
  Client.listeners.each do |listener|
35
38
  listener.call(message)
@@ -37,63 +40,82 @@ module Chromate
37
40
  end
38
41
 
39
42
  @ws.on :open do
40
- puts "Connexion WebSocket établie avec #{@ws_url}"
43
+ Chromate::CLogger.log('Successfully connected to WebSocket', level: :debug)
41
44
  end
42
45
 
43
46
  @ws.on :error do |e|
44
- puts "Erreur WebSocket : #{e.message}"
47
+ Chromate::CLogger.log("WebSocket error: #{e.message}", level: :error)
45
48
  end
46
49
 
47
50
  @ws.on :close do |_e|
48
- puts 'Connexion WebSocket fermée'
51
+ Chromate::CLogger.log('WebSocket connection closed', level: :debug)
49
52
  end
50
53
 
51
- sleep 0.2
54
+ sleep 0.2 # Wait for the connection to be established
52
55
  client_self.send_message('Target.setDiscoverTargets', { discover: true })
56
+
53
57
  client_self
54
58
  end
55
59
 
60
+ # @return [self]
56
61
  def stop
57
62
  @ws&.close
63
+
64
+ self
58
65
  end
59
66
 
67
+ # @param [String] method
68
+ # @param [Hash] params
69
+ # @return [Hash]
60
70
  def send_message(method, params = {})
61
71
  @id += 1
62
72
  message = { id: @id, method: method, params: params }
63
- puts "Envoi du message : #{message}"
73
+ Chromate::CLogger.log("Sending WebSocket message: #{message}", level: :debug)
64
74
 
65
75
  begin
66
76
  @ws.send(message.to_json)
67
77
  @callbacks[@id] = Queue.new
68
78
  result = @callbacks[@id].pop
69
- puts "Réponse reçue pour le message #{message[:id]} : #{result}"
79
+ Chromate::CLogger.log("Response received for message #{message[:id]}: #{result}", level: :debug)
70
80
  result
71
81
  rescue StandardError => e
72
- puts "Erreur WebSocket lors de l'envoi du message : #{e.message}"
82
+ Chromate::CLogger.log("Error sending WebSocket message: #{e.message}", level: :error)
73
83
  reconnect
74
84
  retry
75
85
  end
76
86
  end
77
87
 
88
+ # @return [self]
78
89
  def reconnect
79
90
  @ws_url = fetch_websocket_debug_url
80
91
  @ws = WebSocket::Client::Simple.connect(@ws_url)
81
- puts 'Reconnexion WebSocket réussie'
92
+ Chromate::CLogger.log('Successfully reconnected to WebSocket')
93
+
94
+ self
82
95
  end
83
96
 
97
+ # Allowing different parts to subscribe to WebSocket messages
98
+ # @yieldparam [Hash] message
99
+ # @return [void]
100
+ def on_message(&block)
101
+ Client.listeners << block
102
+ end
103
+
104
+ private
105
+
106
+ # @param [Hash] message
107
+ # @return [self]
84
108
  def handle_message(message)
85
- puts "Message reçu : #{message}"
109
+ Chromate::CLogger.log("Message received: #{message}", level: :debug)
86
110
  return unless message['id'] && @callbacks[message['id']]
87
111
 
88
112
  @callbacks[message['id']].push(message['result'])
89
113
  @callbacks.delete(message['id'])
90
- end
91
114
 
92
- # Allowing different parts to subscribe to WebSocket messages
93
- def on_message(&block)
94
- Client.listeners << block
115
+ self
95
116
  end
96
117
 
118
+ # @return [String]
97
119
  def fetch_websocket_debug_url
98
120
  uri = URI("http://localhost:#{@port}/json/list")
99
121
  response = Net::HTTP.get(uri)
@@ -108,8 +130,8 @@ module Chromate
108
130
  end
109
131
  end
110
132
 
133
+ # @return [String]
111
134
  def create_new_page_target
112
- # Créer une nouvelle page
113
135
  uri = URI("http://localhost:#{@port}/json/new")
114
136
  response = Net::HTTP.get(uri)
115
137
  new_target = JSON.parse(response)