chromate-rb 0.0.1.pre → 0.0.2.pre

Sign up to get free protection for your applications and to get access to all the features.
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)