bidi2pdf 0.1.6 → 0.1.7

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +32 -4
  4. data/README.md +14 -0
  5. data/docker/Dockerfile +1 -1
  6. data/docker/Dockerfile.chromedriver +1 -1
  7. data/docker/Dockerfile.slim +2 -2
  8. data/lib/bidi2pdf/bidi/browser_console_logger.rb +92 -0
  9. data/lib/bidi2pdf/bidi/browser_tab.rb +391 -41
  10. data/lib/bidi2pdf/bidi/client.rb +85 -23
  11. data/lib/bidi2pdf/bidi/command_manager.rb +46 -48
  12. data/lib/bidi2pdf/bidi/commands/base.rb +39 -1
  13. data/lib/bidi2pdf/bidi/commands/browser_remove_user_context.rb +27 -0
  14. data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +4 -0
  15. data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +5 -0
  16. data/lib/bidi2pdf/bidi/commands.rb +1 -0
  17. data/lib/bidi2pdf/bidi/event_manager.rb +1 -1
  18. data/lib/bidi2pdf/bidi/interceptor.rb +1 -1
  19. data/lib/bidi2pdf/bidi/js_logger_helper.rb +16 -0
  20. data/lib/bidi2pdf/bidi/logger_events.rb +25 -45
  21. data/lib/bidi2pdf/bidi/network_event.rb +15 -0
  22. data/lib/bidi2pdf/bidi/network_event_formatters/network_event_console_formatter.rb +4 -3
  23. data/lib/bidi2pdf/bidi/network_events.rb +27 -17
  24. data/lib/bidi2pdf/bidi/session.rb +119 -12
  25. data/lib/bidi2pdf/bidi/user_context.rb +62 -0
  26. data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +7 -7
  27. data/lib/bidi2pdf/chromedriver_manager.rb +48 -21
  28. data/lib/bidi2pdf/cli.rb +10 -2
  29. data/lib/bidi2pdf/dsl.rb +33 -0
  30. data/lib/bidi2pdf/launcher.rb +30 -0
  31. data/lib/bidi2pdf/notifications/event.rb +52 -0
  32. data/lib/bidi2pdf/notifications/instrumenter.rb +65 -0
  33. data/lib/bidi2pdf/notifications/logging_subscriber.rb +136 -0
  34. data/lib/bidi2pdf/notifications.rb +78 -0
  35. data/lib/bidi2pdf/session_runner.rb +35 -3
  36. data/lib/bidi2pdf/verbose_logger.rb +79 -0
  37. data/lib/bidi2pdf/version.rb +1 -1
  38. data/lib/bidi2pdf.rb +99 -7
  39. data/sig/bidi2pdf/bidi/client.rbs +1 -1
  40. metadata +39 -4
  41. data/lib/bidi2pdf/utils.rb +0 -15
@@ -10,23 +10,47 @@ require_relative "commands"
10
10
 
11
11
  module Bidi2pdf
12
12
  module Bidi
13
+ # Represents a WebSocket client for managing communication with a remote server
14
+ # using the Bidi2pdf library. This class handles the setup, connection, and
15
+ # communication with the WebSocket server, including sending commands and
16
+ # handling responses.
17
+ #
18
+ # @example Creating and starting a client
19
+ # client = Bidi2pdf::Bidi::Client.new("ws://example.com/socket")
20
+ # client.start
21
+ #
22
+ # @example Sending a command
23
+ # command = Bidi2pdf::Bidi::Commands::ScriptEvaluate.new context: browsing_context_id, expression: script
24
+ # client.send_cmd(command)
25
+ #
26
+ # @example Subscribing to events
27
+ # client.on_event("eventName") do |event_data|
28
+ # puts "Received event: #{event_data}"
29
+ # end
30
+ #
31
+ # @param [String] ws_url The WebSocket URL to connect to.
13
32
  class Client
14
- include Bidi2pdf::Utils
15
-
33
+ # @return [String] The WebSocket URL.
16
34
  attr_reader :ws_url
17
35
 
36
+ # Initializes a new WebSocket client.
37
+ #
38
+ # @param [String] ws_url The WebSocket URL to connect to.
18
39
  def initialize(ws_url)
19
40
  @ws_url = ws_url
20
41
  @started = false
21
42
  @connection_manager = ConnectionManager.new(logger: Bidi2pdf.logger)
22
43
  end
23
44
 
45
+ # Starts the WebSocket client and establishes a connection.
46
+ #
47
+ # @return [WebSocket::Client::Simple] The WebSocket connection object.
24
48
  def start
25
49
  return @socket if started?
26
50
 
27
51
  WebSocket::Client::Simple.connect(ws_url) do |socket|
28
52
  @socket = socket
29
- @command_manager = CommandManager.new(@socket, logger: Bidi2pdf.logger)
53
+ @command_manager = CommandManager.new(@socket)
30
54
 
31
55
  dispatcher.on_open { @connection_manager.mark_connected }
32
56
  dispatcher.on_message { |data| handle_response_to_cmd(data) }
@@ -38,8 +62,15 @@ module Bidi2pdf
38
62
  @socket
39
63
  end
40
64
 
65
+ # Checks if the WebSocket client has started.
66
+ #
67
+ # @return [Boolean] True if the client has started, false otherwise.
41
68
  def started? = @started
42
69
 
70
+ # Waits until the WebSocket connection is open.
71
+ #
72
+ # @param [Integer] timeout The timeout duration in seconds.
73
+ # @raise [Bidi2pdf::WebsocketError] If the connection is not established within the timeout.
43
74
  def wait_until_open(timeout: Bidi2pdf.default_timeout)
44
75
  @connection_manager.wait_until_open(timeout: timeout)
45
76
  rescue Bidi2pdf::WebsocketError => e
@@ -48,40 +79,72 @@ module Bidi2pdf
48
79
  raise e
49
80
  end
50
81
 
82
+ # Sends a command to the WebSocket server.
83
+ #
84
+ # @param [Bidi2pdf::Bidi::Commands::Base] cmd The command to send.
85
+ # @raise [Bidi2pdf::ClientError] If the client has not started.
51
86
  def send_cmd(cmd)
52
87
  raise Bidi2pdf::ClientError, "Client#start must be called before" unless started?
53
88
 
54
89
  @command_manager.send_cmd(cmd)
55
90
  end
56
91
 
57
- def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout, &block)
92
+ # Sends a command to the WebSocket server and waits for a response.
93
+ #
94
+ # @param [Object] cmd The command to send.
95
+ # @param [Integer] timeout The timeout duration in seconds.
96
+ # @yield [response] A block to handle the response.
97
+ # @raise [Bidi2pdf::ClientError] If the client has not started.
98
+ def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout, &)
58
99
  raise Bidi2pdf::ClientError, "Client#start must be called before" unless started?
59
100
 
60
- timed("Command #{cmd.inspect}") do
61
- @command_manager.send_cmd_and_wait(cmd, timeout: timeout, &block)
62
- end
101
+ @command_manager.send_cmd_and_wait(cmd, timeout: timeout, &)
63
102
  end
64
103
 
65
- def on_message(&block) = dispatcher.on_message(&block)
66
-
67
- def on_open(&block) = dispatcher.on_open(&block)
68
-
69
- def on_close(&block) = dispatcher.on_close(&block)
70
-
71
- def on_error(&block) = dispatcher.on_error(&block)
72
-
104
+ # Registers a callback for incoming WebSocket messages.
105
+ #
106
+ # @yield [message] A block to handle the incoming message.
107
+ def on_message(&) = dispatcher.on_message(&)
108
+
109
+ # Registers a callback for when the WebSocket connection is opened.
110
+ #
111
+ # @yield A block to execute when the connection is opened.
112
+ def on_open(&) = dispatcher.on_open(&)
113
+
114
+ # Registers a callback for when the WebSocket connection is closed.
115
+ #
116
+ # @yield A block to execute when the connection is closed.
117
+ def on_close(&) = dispatcher.on_close(&)
118
+
119
+ # Registers a callback for WebSocket errors.
120
+ #
121
+ # @yield [error] A block to handle the error.
122
+ def on_error(&) = dispatcher.on_error(&)
123
+
124
+ # Subscribes to specific WebSocket events.
125
+ #
126
+ # @param [Array<String>] names The names of the events to subscribe to.
127
+ # @yield [event_data] A block to handle the event data.
73
128
  def on_event(*names, &block)
74
129
  names.each { |name| dispatcher.on_event(name, &block) }
75
130
  cmd = Bidi2pdf::Bidi::Commands::SessionSubscribe.new(events: names)
76
131
  send_cmd(cmd) if names.any?
77
132
  end
78
133
 
134
+ # Removes a message listener.
135
+ #
136
+ # @param [Proc] block The listener block to remove.
79
137
  def remove_message_listener(block) = dispatcher.remove_message_listener(block)
80
138
 
139
+ # Removes event listeners for specific events.
140
+ #
141
+ # @param [Array<String>] names The names of the events to unsubscribe from.
142
+ # @param [Proc] block The listener block to remove.
81
143
  def remove_event_listener(*names, &block)
82
144
  names.each { |event_name| dispatcher.remove_event_listener(event_name, block) }
83
145
  end
84
146
 
147
+ # Closes the WebSocket connection.
85
148
  def close
86
149
  return unless @socket
87
150
 
@@ -93,19 +156,18 @@ module Bidi2pdf
93
156
 
94
157
  private
95
158
 
159
+ # Returns the WebSocket dispatcher for managing events and messages.
160
+ #
161
+ # @return [WebSocketDispatcher] The dispatcher instance.
96
162
  def dispatcher
97
163
  @dispatcher ||= WebSocketDispatcher.new(@socket)
98
164
  end
99
165
 
166
+ # Handles responses to commands sent to the WebSocket server.
167
+ #
168
+ # @param [Hash] data The response data.
100
169
  def handle_response_to_cmd(data)
101
- handled = @command_manager.handle_response(data)
102
- return if handled
103
-
104
- if data["error"]
105
- Bidi2pdf.logger.error "Error response: #{data["error"].inspect}"
106
- else
107
- Bidi2pdf.logger.warn "Unknown response: #{data.inspect}"
108
- end
170
+ @command_manager.handle_response(data)
109
171
  end
110
172
  end
111
173
  end
@@ -14,9 +14,8 @@ module Bidi2pdf
14
14
 
15
15
  initialize_counter
16
16
 
17
- def initialize(socket, logger:)
17
+ def initialize(socket)
18
18
  @socket = socket
19
- @logger = logger
20
19
 
21
20
  @pending_responses = {}
22
21
  @initiated_cmds = {}
@@ -25,30 +24,40 @@ module Bidi2pdf
25
24
  def send_cmd(cmd, store_response: false)
26
25
  id = next_id
27
26
 
28
- if store_response
29
- init_queue_for id
30
- else
31
- @initiated_cmds[id] = true
32
- end
27
+ Bidi2pdf.notification_service.instrument("send_cmd.bidi2pdf", id: id, cmd: cmd) do |instrumentation_payload|
28
+ if store_response
29
+ init_queue_for id
30
+ else
31
+ @initiated_cmds[id] = true
32
+ end
33
+
34
+ payload = cmd.as_payload(id)
33
35
 
34
- payload = cmd.as_payload(id)
36
+ instrumentation_payload[:cmd_payload] = payload
35
37
 
36
- @logger.debug "Sending command: #{redact_sensitive_fields(payload).inspect}"
37
- @socket.send(payload.to_json)
38
+ @socket.send(payload.to_json)
39
+ end
38
40
 
39
41
  id
40
42
  end
41
43
 
42
- def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout)
43
- id = send_cmd(cmd, store_response: true)
44
- response = pop_response id, timeout: timeout
44
+ def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout, &block)
45
+ Bidi2pdf.notification_service.instrument("send_cmd_and_wait.bidi2pdf", cmd: cmd, timeout: timeout) do |instrumentation_payload|
46
+ id = send_cmd(cmd, store_response: true)
45
47
 
46
- raise_timeout_error(id, cmd) if response.nil?
47
- raise CmdError, "Error response: #{response["error"]} #{cmd.inspect}" if response["error"]
48
+ instrumentation_payload[:id] = id
48
49
 
49
- block_given? ? yield(response) : response
50
- ensure
51
- @pending_responses.delete(id)
50
+ response = pop_response id, timeout: timeout
51
+
52
+ instrumentation_payload[:response] = response
53
+
54
+ raise CmdTimeoutError, "Timeout waiting for response to command ID #{id}" if response.nil?
55
+ raise CmdError, "Error response: #{response["error"]} #{cmd.inspect}" if response["error"]
56
+
57
+ block ? block.call(response) : response
58
+ ensure
59
+ @pending_responses.delete(id)
60
+ end
52
61
  end
53
62
 
54
63
  def pop_response(id, timeout:)
@@ -60,45 +69,34 @@ module Bidi2pdf
60
69
  end
61
70
 
62
71
  def handle_response(data)
63
- if (id = data["id"])
64
- if @pending_responses.key?(id)
65
- @pending_responses[id]&.push(data)
66
- return true
67
- elsif @initiated_cmds.key?(id)
68
- @logger.error "Received error: #{data["error"]} for cmd: #{id}" if data["error"]
69
-
70
- return @initiated_cmds.delete(id)
71
- end
72
- end
72
+ Bidi2pdf.notification_service.instrument("handle_response.bidi2pdf", data: data) do |instrumentation_payload|
73
+ instrumentation_payload[:error] = data["error"] if data["error"]
73
74
 
74
- false
75
- end
75
+ if (id = data["id"])
76
+ instrumentation_payload[:handled] = true
77
+ instrumentation_payload[:id] = id
76
78
 
77
- private
79
+ if @pending_responses.key?(id)
80
+ @pending_responses[id]&.push(data)
81
+ return true
82
+ elsif @initiated_cmds.key?(id)
83
+ @initiated_cmds.delete(id)
78
84
 
79
- def init_queue_for(id) = @pending_responses[id] = Thread::Queue.new
85
+ return true
86
+ end
87
+ end
80
88
 
81
- def next_id = self.class.next_id
89
+ instrumentation_payload[:handled] = false
82
90
 
83
- def redact_sensitive_fields(obj, sensitive_keys = %w[value token password authorization username])
84
- case obj
85
- when Hash
86
- obj.transform_values.with_index do |v, idx|
87
- k = obj.keys[idx]
88
- sensitive_keys.include?(k.to_s.downcase) ? "[REDACTED]" : redact_sensitive_fields(v, sensitive_keys)
89
- end
90
- when Array
91
- obj.map { |item| redact_sensitive_fields(item, sensitive_keys) }
92
- else
93
- obj
91
+ false
94
92
  end
95
93
  end
96
94
 
97
- def raise_timeout_error(id, cmd)
98
- @logger.error "Timeout waiting for response to command #{id}, cmd: #{cmd.inspect}"
95
+ private
99
96
 
100
- raise CmdTimeoutError, "Timeout waiting for response to command ID #{id}"
101
- end
97
+ def init_queue_for(id) = @pending_responses[id] = Thread::Queue.new
98
+
99
+ def next_id = self.class.next_id
102
100
  end
103
101
  end
104
102
  end
@@ -3,11 +3,25 @@
3
3
  module Bidi2pdf
4
4
  module Bidi
5
5
  module Commands
6
+ # Base module for defining WebSocket commands in the Bidi2pdf library.
7
+ # This module provides common functionality for creating, comparing, and
8
+ # inspecting WebSocket command payloads.
6
9
  module Base
10
+ # Abstract method that must be implemented in subclasses to define the
11
+ # WebSocket command method name.
12
+ #
13
+ # @raise [NotImplementedError] If the method is not implemented in a subclass.
7
14
  def method_name = raise(NotImplementedError, "method_name must be implemented in subclass")
8
15
 
16
+ # Returns the parameters for the WebSocket command.
17
+ #
18
+ # @return [Hash] The parameters for the command. Defaults to an empty hash.
9
19
  def params = {}
10
20
 
21
+ # Constructs the payload for the WebSocket command.
22
+ #
23
+ # @param [Integer] id The unique identifier for the command.
24
+ # @return [Hash] The payload containing the command ID, method name, and parameters.
11
25
  def as_payload(id)
12
26
  {
13
27
  id: id,
@@ -16,6 +30,10 @@ module Bidi2pdf
16
30
  }
17
31
  end
18
32
 
33
+ # Compares the current command with another command for equality.
34
+ #
35
+ # @param [Object] other The other command to compare.
36
+ # @return [Boolean] True if the commands are equal, false otherwise.
19
37
  # rubocop: disable Metrics/AbcSize
20
38
  def ==(other)
21
39
  return false unless other.respond_to?(:method_name) && other.respond_to?(:params)
@@ -29,17 +47,26 @@ module Bidi2pdf
29
47
 
30
48
  # rubocop: enable Metrics/AbcSize
31
49
 
32
- # Hash equality comparison
50
+ # Checks if the current command is hash-equal to another command.
51
+ #
52
+ # @param [Object] other The other command to compare.
53
+ # @return [Boolean] True if the commands are hash-equal, false otherwise.
33
54
  def eql?(other)
34
55
  return false unless other.is_a?(Bidi2pdf::Bidi::Commands::Base)
35
56
 
36
57
  self == other
37
58
  end
38
59
 
60
+ # Computes the hash value for the command.
61
+ #
62
+ # @return [Integer] The hash value based on the method name and parameters.
39
63
  def hash
40
64
  [method_name, params].hash
41
65
  end
42
66
 
67
+ # Returns a string representation of the command, with sensitive fields redacted.
68
+ #
69
+ # @return [String] The string representation of the command.
43
70
  def inspect
44
71
  attributes = redact_sensitive_fields({ method_name: method_name, params: params })
45
72
 
@@ -48,6 +75,11 @@ module Bidi2pdf
48
75
 
49
76
  private
50
77
 
78
+ # Redacts sensitive fields in a given object.
79
+ #
80
+ # @param [Object] obj The object to redact.
81
+ # @param [Array<String>] sensitive_keys The list of sensitive keys to redact. Defaults to common sensitive keys.
82
+ # @return [Object] The object with sensitive fields redacted.
51
83
  def redact_sensitive_fields(obj, sensitive_keys = %w[value token password authorization username])
52
84
  case obj
53
85
  when Hash
@@ -62,6 +94,12 @@ module Bidi2pdf
62
94
  end
63
95
  end
64
96
 
97
+ # Logs and raises a timeout error for a command.
98
+ #
99
+ # @param [Integer] id The unique identifier for the command.
100
+ # @param [String] method The method name of the command.
101
+ # @param [Hash] params The parameters of the command.
102
+ # @raise [CmdTimeoutError] If the command times out.
65
103
  def raise_timeout_error(id, method, params)
66
104
  @logger.error "Timeout waiting for response to command #{id}, cmd: #{method}, params: #{redact_sensitive_fields(params).inspect}"
67
105
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class BrowserRemoveUserContext
7
+ include Base
8
+
9
+ attr_reader :user_context_id
10
+
11
+ def initialize(user_context_id: nil)
12
+ @user_context_id = user_context_id
13
+ end
14
+
15
+ def params
16
+ {
17
+ userContext: @user_context_id
18
+ }.compact
19
+ end
20
+
21
+ def method_name
22
+ "browser.removeUserContext"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -13,6 +13,10 @@ module Bidi2pdf
13
13
  @print_options = print_options || { background: true }
14
14
 
15
15
  PrintParametersValidator.validate!(@print_options)
16
+
17
+ return unless @print_options[:page]&.key?(:format)
18
+
19
+ @print_options[:page] = Bidi2pdf.translate_paper_format @print_options[:page][:format]
16
20
  end
17
21
 
18
22
  def params
@@ -17,6 +17,7 @@ module Bidi2pdf
17
17
  # },
18
18
  # orientation: "portrait" or "landscape" (optional, default: "portrait"),
19
19
  # page: {
20
+ # format: String (optional, use either format or width/height),
20
21
  # width: Float >= 0.0352 (optional, default: 21.59),
21
22
  # height: Float >= 0.0352 (optional, default: 27.94)
22
23
  # },
@@ -97,18 +98,22 @@ module Bidi2pdf
97
98
  end
98
99
  end
99
100
 
101
+ # rubocop: disable Metrics/CyclomaticComplexity
100
102
  def validate_page_size
101
103
  return unless @params.key?(:page)
102
104
 
103
105
  page = @params[:page]
104
106
  raise ArgumentError, ":page must be a Hash" unless page.is_a?(Hash)
105
107
 
108
+ Bidi2pdf.translate_paper_format @params[:page][:format] if @params[:page][:format]
109
+
106
110
  %i[width height].each do |dim|
107
111
  next unless page.key?(dim)
108
112
 
109
113
  val = page[dim]
110
114
  raise ArgumentError, "page[:#{dim}] must be a float >= 0.0352" unless val.is_a?(Numeric) && val >= 0.0352
111
115
  end
116
+ # rubocop: enable Metrics/CyclomaticComplexity
112
117
  end
113
118
  end
114
119
  end
@@ -13,6 +13,7 @@ module Bidi2pdf
13
13
  require_relative "commands/get_user_contexts"
14
14
  require_relative "commands/script_evaluate"
15
15
  require_relative "commands/browser_create_user_context"
16
+ require_relative "commands/browser_remove_user_context"
16
17
  require_relative "commands/browser_close"
17
18
  require_relative "commands/browsing_context_close"
18
19
  require_relative "commands/browsing_context_navigate"
@@ -43,7 +43,7 @@ module Bidi2pdf
43
43
 
44
44
  def log_msg(prefix, data)
45
45
  message = truncate_large_values(data)
46
- Bidi2pdf.logger.debug "#{prefix}: #{message.inspect}"
46
+ Bidi2pdf.logger.debug3 "#{prefix}: #{message.inspect}"
47
47
  end
48
48
 
49
49
  # rubocop: disable all
@@ -46,7 +46,7 @@ module Bidi2pdf
46
46
  url = event_response["request"]["url"]
47
47
 
48
48
  # Log the interception
49
- Bidi2pdf.logger.debug "Interceptor #{interceptor_id} handling event: #{navigation_id}/#{network_id}/#{url}"
49
+ Bidi2pdf.logger.debug1 "Interceptor #{interceptor_id} handling event: #{navigation_id}/#{network_id}/#{url}"
50
50
 
51
51
  process_interception(event_response, navigation_id, network_id, url)
52
52
  rescue StandardError => e
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module JsLoggerHelper
6
+ private
7
+
8
+ def format_stack_trace(trace)
9
+ trace["callFrames"].each_with_index.map do |frame, index|
10
+ function = frame["functionName"].to_s.empty? ? "(anonymous)" : frame["functionName"]
11
+ "##{index} #{function} at #{frame["url"]}:#{frame["lineNumber"]}:#{frame["columnNumber"]}"
12
+ end.join("\n")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "network_event"
4
+ require_relative "browser_console_logger"
4
5
 
5
6
  module Bidi2pdf
6
7
  module Bidi
7
8
  class LoggerEvents
8
- attr_reader :context_id
9
+ attr_reader :context_id, :browser_console_logger
9
10
 
10
11
  def initialize(context_id)
11
12
  @context_id = context_id
13
+ @browser_console_logger = BrowserConsoleLogger.new(Bidi2pdf.browser_console_logger)
12
14
  end
13
15
 
14
16
  def handle_event(data)
@@ -18,9 +20,11 @@ module Bidi2pdf
18
20
  if event.dig("source", "context") == context_id
19
21
  handle_response(method, event)
20
22
  else
21
- Bidi2pdf.logger.debug "Ignoring Log event: #{method}, context_id: #{context_id}, params: #{event}"
23
+ # this should be Bidi2pdf.logger and not Bidi2pdf.browser_console_logger
24
+ Bidi2pdf.logger.debug2 "Ignoring Log event: #{method}, context_id: #{context_id}, params: #{event}"
22
25
  end
23
26
  rescue StandardError => e
27
+ # this should be Bidi2pdf.logger and not Bidi2pdf.browser_console_logger
24
28
  Bidi2pdf.logger.error "Error handling Log event: #{e.message}\n#{e.backtrace&.join("\n")}"
25
29
  end
26
30
 
@@ -29,58 +33,34 @@ module Bidi2pdf
29
33
  text = event["text"]
30
34
  args = event["args"] || []
31
35
  stack_trace = event["stackTrace"]
32
- timestamp = format_timestamp(event["timestamp"])
33
- prefix = log_prefix(timestamp)
34
-
35
- log_message(level, prefix, text)
36
- log_args(prefix, args)
37
- log_stack_trace(prefix, stack_trace) if stack_trace && level == :error
38
- end
39
-
40
- private
41
-
42
- def log_message(level, prefix, text)
43
- return unless text
44
-
45
- Bidi2pdf.logger.send(level, "#{prefix} #{text}")
46
- end
47
-
48
- def log_args(prefix, args)
49
- return if args.empty?
50
-
51
- Bidi2pdf.logger.debug("#{prefix} Args: #{args.inspect}")
52
- end
53
-
54
- def log_stack_trace(prefix, trace)
55
- formatted_trace = format_stack_trace(trace)
56
- Bidi2pdf.logger.error("#{prefix} Stack trace captured:\n#{formatted_trace}")
57
- end
58
-
59
- def format_timestamp(timestamp)
60
- return "N/A" unless timestamp
61
-
62
- Time.at(timestamp.to_f / 1000).utc.strftime("%Y-%m-%d %H:%M:%S.%L UTC")
63
- end
64
-
65
- def format_stack_trace(trace)
66
- trace["callFrames"].each_with_index.map do |frame, index|
67
- function = frame["functionName"].to_s.empty? ? "(anonymous)" : frame["functionName"]
68
- "##{index} #{function} at #{frame["url"]}:#{frame["lineNumber"]}:#{frame["columnNumber"]}"
69
- end.join("\n")
36
+ timestamp = event["timestamp"]
37
+
38
+ Bidi2pdf.notification_service.instrument("browser_console_log_received.bidi2pdf",
39
+ {
40
+ level: level,
41
+ text: text,
42
+ args: args,
43
+ stack_trace: stack_trace,
44
+ timestamp: timestamp
45
+ })
46
+
47
+ browser_console_logger.builder
48
+ .with_level(level)
49
+ .with_timestamp(timestamp)
50
+ .with_text(text)
51
+ .with_args(args)
52
+ .with_stack_trace(stack_trace)
53
+ .log_event
70
54
  end
71
55
 
72
56
  def resolve_log_level(js_level)
73
57
  case js_level
74
- when "info", "warn", "error"
58
+ when "info", "warn", "error", "trace"
75
59
  js_level.to_sym
76
60
  else
77
61
  :debug
78
62
  end
79
63
  end
80
-
81
- def log_prefix(timestamp)
82
- "[#{timestamp}][Browser Console Log]"
83
- end
84
64
  end
85
65
  end
86
66
  end
@@ -67,6 +67,21 @@ module Bidi2pdf
67
67
  "end=#{end_str}, " \
68
68
  "duration=#{took_str}>"
69
69
  end
70
+
71
+ def dup
72
+ self.class.new(
73
+ id: @id,
74
+ url: @url,
75
+ timestamp: @start_timestamp,
76
+ timing: @timing&.dup,
77
+ state: @state,
78
+ http_status_code: @http_status_code,
79
+ http_method: @http_method
80
+ ).tap do |duped|
81
+ duped.instance_variable_set(:@end_timestamp, @end_timestamp)
82
+ duped.instance_variable_set(:@bytes_received, @bytes_received)
83
+ end
84
+ end
70
85
  end
71
86
  end
72
87
  end
@@ -7,7 +7,7 @@ module Bidi2pdf
7
7
  class NetworkEventConsoleFormatter
8
8
  include NetworkEventFormatterUtils
9
9
 
10
- attr_reader :color_enabled
10
+ attr_reader :color_enabled, :logger
11
11
 
12
12
  # ANSI styles
13
13
  RESET = "\e[0m"
@@ -19,12 +19,13 @@ module Bidi2pdf
19
19
  CYAN = "\e[36m"
20
20
  GRAY = "\e[90m"
21
21
 
22
- def initialize(color: true)
22
+ def initialize(color: true, logger: Bidi2pdf.network_events_logger)
23
23
  @color_enabled = color
24
+ @logger = logger
24
25
  end
25
26
 
26
27
  def log(events)
27
- events.each { |event| pretty_log(event).each_line { |line| Bidi2pdf.network_events_logger.info(line.chomp) } }
28
+ events.each { |event| pretty_log(event).each_line { |line| logger.info(line.chomp) } }
28
29
  end
29
30
 
30
31
  def pretty_log(event)