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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +32 -4
- data/README.md +14 -0
- data/docker/Dockerfile +1 -1
- data/docker/Dockerfile.chromedriver +1 -1
- data/docker/Dockerfile.slim +2 -2
- data/lib/bidi2pdf/bidi/browser_console_logger.rb +92 -0
- data/lib/bidi2pdf/bidi/browser_tab.rb +391 -41
- data/lib/bidi2pdf/bidi/client.rb +85 -23
- data/lib/bidi2pdf/bidi/command_manager.rb +46 -48
- data/lib/bidi2pdf/bidi/commands/base.rb +39 -1
- data/lib/bidi2pdf/bidi/commands/browser_remove_user_context.rb +27 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +4 -0
- data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +5 -0
- data/lib/bidi2pdf/bidi/commands.rb +1 -0
- data/lib/bidi2pdf/bidi/event_manager.rb +1 -1
- data/lib/bidi2pdf/bidi/interceptor.rb +1 -1
- data/lib/bidi2pdf/bidi/js_logger_helper.rb +16 -0
- data/lib/bidi2pdf/bidi/logger_events.rb +25 -45
- data/lib/bidi2pdf/bidi/network_event.rb +15 -0
- data/lib/bidi2pdf/bidi/network_event_formatters/network_event_console_formatter.rb +4 -3
- data/lib/bidi2pdf/bidi/network_events.rb +27 -17
- data/lib/bidi2pdf/bidi/session.rb +119 -12
- data/lib/bidi2pdf/bidi/user_context.rb +62 -0
- data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +7 -7
- data/lib/bidi2pdf/chromedriver_manager.rb +48 -21
- data/lib/bidi2pdf/cli.rb +10 -2
- data/lib/bidi2pdf/dsl.rb +33 -0
- data/lib/bidi2pdf/launcher.rb +30 -0
- data/lib/bidi2pdf/notifications/event.rb +52 -0
- data/lib/bidi2pdf/notifications/instrumenter.rb +65 -0
- data/lib/bidi2pdf/notifications/logging_subscriber.rb +136 -0
- data/lib/bidi2pdf/notifications.rb +78 -0
- data/lib/bidi2pdf/session_runner.rb +35 -3
- data/lib/bidi2pdf/verbose_logger.rb +79 -0
- data/lib/bidi2pdf/version.rb +1 -1
- data/lib/bidi2pdf.rb +99 -7
- data/sig/bidi2pdf/bidi/client.rbs +1 -1
- metadata +39 -4
- data/lib/bidi2pdf/utils.rb +0 -15
data/lib/bidi2pdf/bidi/client.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
36
|
+
instrumentation_payload[:cmd_payload] = payload
|
35
37
|
|
36
|
-
|
37
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
47
|
-
raise CmdError, "Error response: #{response["error"]} #{cmd.inspect}" if response["error"]
|
48
|
+
instrumentation_payload[:id] = id
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
64
|
-
if
|
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
|
-
|
75
|
-
|
75
|
+
if (id = data["id"])
|
76
|
+
instrumentation_payload[:handled] = true
|
77
|
+
instrumentation_payload[:id] = id
|
76
78
|
|
77
|
-
|
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
|
-
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
end
|
80
88
|
|
81
|
-
|
89
|
+
instrumentation_payload[:handled] = false
|
82
90
|
|
83
|
-
|
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
|
-
|
98
|
-
@logger.error "Timeout waiting for response to command #{id}, cmd: #{cmd.inspect}"
|
95
|
+
private
|
99
96
|
|
100
|
-
|
101
|
-
|
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
|
-
#
|
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"
|
@@ -46,7 +46,7 @@ module Bidi2pdf
|
|
46
46
|
url = event_response["request"]["url"]
|
47
47
|
|
48
48
|
# Log the interception
|
49
|
-
Bidi2pdf.logger.
|
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
|
-
|
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 =
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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|
|
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)
|