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/launcher.rb
CHANGED
@@ -5,6 +5,36 @@ require_relative "session_runner"
|
|
5
5
|
require_relative "bidi/session"
|
6
6
|
|
7
7
|
module Bidi2pdf
|
8
|
+
# Represents a launcher for managing browser sessions and executing tasks
|
9
|
+
# using the Bidi2pdf library. This class handles the setup and teardown
|
10
|
+
# of browser sessions, as well as the execution of tasks within those sessions.
|
11
|
+
#
|
12
|
+
# @example Launching a session
|
13
|
+
# launcher = Bidi2pdf::Launcher.new(
|
14
|
+
# url: "http://example.com",
|
15
|
+
# inputfile: "input.pdf",
|
16
|
+
# output: "output.pdf",
|
17
|
+
# cookies: [],
|
18
|
+
# headers: {},
|
19
|
+
# auth: nil,
|
20
|
+
# headless: true
|
21
|
+
# )
|
22
|
+
# launcher.launch
|
23
|
+
# launcher.stop
|
24
|
+
#
|
25
|
+
# @param [String] url The URL to navigate to in the browser session.
|
26
|
+
# @param [String] inputfile The path to the input file to be processed.
|
27
|
+
# @param [String] output The path to the output file to be generated.
|
28
|
+
# @param [Array<Hash>] cookies An array of cookies to set in the browser session.
|
29
|
+
# @param [Hash] headers A hash of HTTP headers to include in the browser session.
|
30
|
+
# @param [Hash, nil] auth Authentication credentials (e.g., username and password).
|
31
|
+
# @param [Boolean] headless Whether to run the browser in headless mode. Defaults to true.
|
32
|
+
# @param [Integer] port The port to use for the browser session. Defaults to 0.
|
33
|
+
# @param [Boolean] wait_window_loaded Whether to wait for the window to fully load. Defaults to false.
|
34
|
+
# @param [Boolean] wait_network_idle Whether to wait for the network to become idle. Defaults to false.
|
35
|
+
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
36
|
+
# @param [String, nil] remote_browser_url The URL of a remote browser to connect to. Defaults to nil.
|
37
|
+
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
8
38
|
class Launcher
|
9
39
|
# rubocop:disable Metrics/ParameterLists
|
10
40
|
def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdf
|
4
|
+
# rubocop: disable Lint/RescueException
|
5
|
+
module Notifications
|
6
|
+
class Event
|
7
|
+
attr_reader :name, :transaction_id
|
8
|
+
attr_accessor :payload
|
9
|
+
|
10
|
+
def initialize(name, start, ending, transaction_id, payload)
|
11
|
+
@name = name
|
12
|
+
@payload = payload
|
13
|
+
@time = start ? start.to_f * 1_000.0 : start
|
14
|
+
@transaction_id = transaction_id
|
15
|
+
@end = ending ? ending.to_f * 1_000.0 : ending
|
16
|
+
end
|
17
|
+
|
18
|
+
def record # :nodoc:
|
19
|
+
start!
|
20
|
+
begin
|
21
|
+
yield payload if block_given?
|
22
|
+
rescue Exception => e
|
23
|
+
payload[:exception] = [e.class.name, e.message]
|
24
|
+
payload[:exception_object] = e
|
25
|
+
raise e
|
26
|
+
ensure
|
27
|
+
finish!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def start! = @time = now
|
32
|
+
|
33
|
+
def finish! = @end = now
|
34
|
+
|
35
|
+
def duration = @end - @time
|
36
|
+
|
37
|
+
def time
|
38
|
+
@time / 1000.0 if @time
|
39
|
+
end
|
40
|
+
|
41
|
+
def end
|
42
|
+
@end / 1000.0 if @end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# rubocop: enable Lint/RescueException
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Bidi2pdf
|
6
|
+
# This module provides a way to instrument events in the Bidi2pdf library.
|
7
|
+
# It it's heavyly inspired by ActiveSupport::Notifications.
|
8
|
+
# and thought to be used in a similar way.
|
9
|
+
# In Rails environment, ActiveSupport::Notifications should be use instead.
|
10
|
+
# via configuration: Bidi2pdf.notification_service = ActiveSupport::Notifications
|
11
|
+
|
12
|
+
# rubocop: disable Lint/RescueException, Lint/SuppressedException
|
13
|
+
module Notifications
|
14
|
+
class Instrumenter
|
15
|
+
attr_reader :id
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@id = SecureRandom.uuid
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify(name, payload, &)
|
22
|
+
event = create_event(name, payload)
|
23
|
+
result = nil
|
24
|
+
begin
|
25
|
+
result = event.record(&)
|
26
|
+
rescue Exception => e
|
27
|
+
end
|
28
|
+
|
29
|
+
subscriber_exceptions = notify_subscribers(name, event)
|
30
|
+
|
31
|
+
raise Bidi2pdf::NotificationsError.new(subscriber_exceptions), cause: subscriber_exceptions.first if subscriber_exceptions.any?
|
32
|
+
raise e if e
|
33
|
+
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def create_event(name, payload)
|
40
|
+
Event.new(name, nil, nil, @id, payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Style/CaseEquality
|
44
|
+
def notify_subscribers(name, event)
|
45
|
+
exceptions = []
|
46
|
+
|
47
|
+
Notifications.subscribers.each do |pattern, blocks|
|
48
|
+
next unless pattern === name
|
49
|
+
|
50
|
+
blocks.each do |subscriber|
|
51
|
+
subscriber.call(event)
|
52
|
+
rescue Exception => e
|
53
|
+
exceptions << e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
exceptions
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:enable Style/CaseEquality
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# rubocop: enable Lint/RescueException, Lint/SuppressedException
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdf
|
4
|
+
module Notifications
|
5
|
+
# for reuse within ActiveSupport::LogSubscriber
|
6
|
+
module LoggingSubscriberActions
|
7
|
+
def handle_response(event)
|
8
|
+
payload = event.payload
|
9
|
+
|
10
|
+
if payload[:error]
|
11
|
+
logger.error "Received error: #{payload[:error].inspect} for cmd: #{payload[:id] || "-"}"
|
12
|
+
elsif !payload[:handled]
|
13
|
+
Bidi2pdf.logger.warn "Unknown response: #{payload[:data].inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_cmd(event)
|
18
|
+
logger.debug "Sending command: #{event.payload[:cmd].method_name} id: ##{event.payload[:cmd_payload][:id]}"
|
19
|
+
|
20
|
+
logger.debug1 do
|
21
|
+
payload = redact_sensitive_fields(event.payload[:cmd_payload])
|
22
|
+
"Sending command: #{payload.inspect} (#{event.duration.round(1)}ms)"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_cmd_and_wait(event)
|
27
|
+
return unless event.payload[:exception]
|
28
|
+
|
29
|
+
payload = redact_sensitive_fields(event.payload[:cmd]&.params || {})
|
30
|
+
logger.error "Error sending command: #{payload} (#{event.duration.round(1)}ms) - #{event.payload[:exception].inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def session_close(event)
|
34
|
+
return unless event.payload[:error]
|
35
|
+
|
36
|
+
logger.error "Session close error: #{event.payload[:error].inspect}, attempt: #{event.payload[:attempt]}, retry: #{event.payload[:retry]}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# rubocop: disable Metrics/AbcSize
|
40
|
+
def network_event_received(event)
|
41
|
+
return unless logger.debug2?
|
42
|
+
|
43
|
+
msg = case event.payload[:method]
|
44
|
+
when "network.beforeRequestSent"
|
45
|
+
"Request url '#{event.payload[:url]}' started"
|
46
|
+
|
47
|
+
when "network.responseStarted"
|
48
|
+
nil
|
49
|
+
when "network.responseCompleted"
|
50
|
+
"Request url '#{event.payload[:url]}' completed"
|
51
|
+
when "network.fetchError"
|
52
|
+
"Request url '#{event.payload[:url]}' error."
|
53
|
+
else
|
54
|
+
"Unknown network event: #{event.payload[:method]} for url '#{event.payload[:url]}'"
|
55
|
+
end
|
56
|
+
|
57
|
+
logger.debug2 msg if msg
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
61
|
+
def network_idle(event)
|
62
|
+
return unless logger.info?
|
63
|
+
|
64
|
+
requests = event.payload[:requests]
|
65
|
+
transfered = requests.map { |request| request.bytes_received || 0 }.sum
|
66
|
+
status_counts = requests
|
67
|
+
.group_by { |evt| evt.http_status_code || 0 }
|
68
|
+
.transform_keys { |code| code.zero? || code.nil? ? "pending" : code.to_s }
|
69
|
+
.transform_values(&:count)
|
70
|
+
.map { |code, count| "#{code}: #{count}" }
|
71
|
+
.join(", ")
|
72
|
+
|
73
|
+
logger.info "Network was idle after #{event.duration.round(1)}ms, #{requests.size} requests, " \
|
74
|
+
"transferred #{transfered} bytes (status codes: #{status_counts})"
|
75
|
+
end
|
76
|
+
|
77
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
78
|
+
|
79
|
+
def page_loaded(event)
|
80
|
+
logger.info "Page loaded: #{event.duration.round(1)}ms"
|
81
|
+
end
|
82
|
+
|
83
|
+
def print(event)
|
84
|
+
logger.info "Page printed: #{event.duration.round(1)}ms"
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def redact_sensitive_fields(obj, sensitive_keys = %w[value token password authorization username])
|
90
|
+
case obj
|
91
|
+
when Hash
|
92
|
+
obj.transform_values.with_index do |v, idx|
|
93
|
+
k = obj.keys[idx]
|
94
|
+
sensitive_keys.include?(k.to_s.downcase) ? "[REDACTED]" : redact_sensitive_fields(v, sensitive_keys)
|
95
|
+
end
|
96
|
+
when Array
|
97
|
+
obj.map { |item| redact_sensitive_fields(item, sensitive_keys) }
|
98
|
+
else
|
99
|
+
obj
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class LoggingSubscriber
|
105
|
+
include LoggingSubscriberActions
|
106
|
+
|
107
|
+
attr_accessor :logger
|
108
|
+
|
109
|
+
# rubocop: disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
110
|
+
def initialize(logger: Logger.new($stdout))
|
111
|
+
@logger = logger
|
112
|
+
Bidi2pdf.notification_service&.subscribe("handle_response.bidi2pdf", &method(:handle_response))
|
113
|
+
Bidi2pdf.notification_service&.subscribe("send_cmd.bidi2pdf", &method(:send_cmd))
|
114
|
+
Bidi2pdf.notification_service&.subscribe("send_cmd_and_wait.bidi2pdf", &method(:send_cmd_and_wait))
|
115
|
+
Bidi2pdf.notification_service&.subscribe("session_close.bidi2pdf", &method(:session_close))
|
116
|
+
Bidi2pdf.notification_service&.subscribe("network_idle.bidi2pdf", &method(:network_idle))
|
117
|
+
Bidi2pdf.notification_service&.subscribe("page_loaded.bidi2pdf", &method(:page_loaded))
|
118
|
+
Bidi2pdf.notification_service&.subscribe("network_event_received.bidi2pdf", &method(:network_event_received))
|
119
|
+
Bidi2pdf.notification_service&.subscribe("print.bidi2pdf", &method(:network_event_received))
|
120
|
+
end
|
121
|
+
|
122
|
+
def unsubscribe
|
123
|
+
Bidi2pdf.notification_service&.unsubscribe("handle_response.bidi2pdf", &method(:handle_response))
|
124
|
+
Bidi2pdf.notification_service&.unsubscribe("send_cmd.bidi2pdf", &method(:send_cmd))
|
125
|
+
Bidi2pdf.notification_service&.unsubscribe("send_cmd_and_wait.bidi2pdf", &method(:send_cmd_and_wait))
|
126
|
+
Bidi2pdf.notification_service&.unsubscribe("session_close.bidi2pdf", &method(:session_close))
|
127
|
+
Bidi2pdf.notification_service&.unsubscribe("network_idle.bidi2pdf", &method(:network_idle))
|
128
|
+
Bidi2pdf.notification_service&.unsubscribe("page_loaded.bidi2pdf", &method(:page_loaded))
|
129
|
+
Bidi2pdf.notification_service&.unsubscribe("network_event_received.bidi2pdf", &method(:network_event_received))
|
130
|
+
Bidi2pdf.notification_service&.unsubscribe("print.bidi2pdf", &method(:network_event_received))
|
131
|
+
end
|
132
|
+
|
133
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "chromedriver_manager"
|
4
|
+
require_relative "session_runner"
|
5
|
+
require_relative "bidi/session"
|
6
|
+
require_relative "notifications/event"
|
7
|
+
require_relative "notifications/instrumenter"
|
8
|
+
|
9
|
+
require "securerandom"
|
10
|
+
|
11
|
+
module Bidi2pdf
|
12
|
+
# This module provides a way to instrument events in the Bidi2pdf library.
|
13
|
+
# It it's heavyly inspired by ActiveSupport::Notifications.
|
14
|
+
# and thought to be used in a similar way.
|
15
|
+
# In Rails environment, ActiveSupport::Notifications should be used instead.
|
16
|
+
# via configuration: config.notification_service = ActiveSupport::Notifications
|
17
|
+
|
18
|
+
module Notifications
|
19
|
+
Thread.attr_accessor :bidi2pdf_notification_instrumenter
|
20
|
+
|
21
|
+
@subscribers = Hash.new { |h, k| h[k] = [] }
|
22
|
+
|
23
|
+
class << self
|
24
|
+
attr_reader :subscribers
|
25
|
+
|
26
|
+
def instrument(name, payload = {})
|
27
|
+
payload = payload.dup
|
28
|
+
|
29
|
+
if listening?(name)
|
30
|
+
notify(name, payload) { yield payload if block_given? }
|
31
|
+
elsif block_given?
|
32
|
+
yield payload
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def subscribe(event_pattern, &block)
|
37
|
+
pattern = normalize_pattern(event_pattern)
|
38
|
+
|
39
|
+
@subscribers[pattern] << block
|
40
|
+
|
41
|
+
block
|
42
|
+
end
|
43
|
+
|
44
|
+
def unsubscribe(event_pattern, block = nil)
|
45
|
+
pattern = normalize_pattern(event_pattern)
|
46
|
+
|
47
|
+
if block
|
48
|
+
@subscribers[pattern].delete(block)
|
49
|
+
else
|
50
|
+
@subscribers[pattern].clear
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# rubocop: disable Style/CaseEquality
|
55
|
+
def listening?(name)
|
56
|
+
@subscribers.any? do |pattern, blocks|
|
57
|
+
pattern === name && blocks.any?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# rubocop: enable Style/CaseEquality
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def bidi2pdf_notification_instrumenter = Thread.current.bidi2pdf_notification_instrumenter ||= Instrumenter.new
|
66
|
+
|
67
|
+
def notify(name, payload, &) = bidi2pdf_notification_instrumenter.notify(name, payload, &)
|
68
|
+
|
69
|
+
def normalize_pattern(pat)
|
70
|
+
case pat
|
71
|
+
when String, Regexp then pat
|
72
|
+
else
|
73
|
+
raise ArgumentError, "Pattern must be String or Regexp"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,6 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Bidi2pdf
|
4
|
+
# Represents a runner for managing browser sessions and executing tasks
|
5
|
+
# using the Bidi2pdf library. This class handles the setup, configuration,
|
6
|
+
# and execution of browser-related workflows, including navigation, cookie
|
7
|
+
# management, and printing.
|
8
|
+
#
|
9
|
+
# @example Running a session
|
10
|
+
# session_runner = Bidi2pdf::SessionRunner.new(
|
11
|
+
# session: session,
|
12
|
+
# url: "http://example.com",
|
13
|
+
# inputfile: "input.html",
|
14
|
+
# output: "output.pdf",
|
15
|
+
# cookies: { "key" => "value" },
|
16
|
+
# headers: { "Authorization" => "Bearer token" },
|
17
|
+
# auth: { username: "user", password: "pass" },
|
18
|
+
# wait_window_loaded: true,
|
19
|
+
# wait_network_idle: true,
|
20
|
+
# print_options: { landscape: true },
|
21
|
+
# network_log_format: :json
|
22
|
+
# )
|
23
|
+
# session_runner.run
|
24
|
+
#
|
25
|
+
# @param [Object] session The browser session object to use.
|
26
|
+
# @param [String, nil] url The URL to navigate to in the browser session.
|
27
|
+
# @param [String, nil] inputfile The path to the input file to be processed if no URL is provided.
|
28
|
+
# @param [String, nil] output The path to the output file to be generated.
|
29
|
+
# @param [Hash] cookies A hash of cookies to set in the browser session. Defaults to an empty hash.
|
30
|
+
# @param [Hash] headers A hash of HTTP headers to include in the browser session. Defaults to an empty hash.
|
31
|
+
# @param [Hash, nil] auth Authentication credentials (e.g., username and password). Defaults to an empty hash.
|
32
|
+
# @param [Boolean] wait_window_loaded Whether to wait for the window to fully load. Defaults to false.
|
33
|
+
# @param [Boolean] wait_network_idle Whether to wait for the network to become idle. Defaults to false.
|
34
|
+
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
35
|
+
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
4
36
|
class SessionRunner
|
5
37
|
# rubocop: disable Metrics/ParameterLists
|
6
38
|
def initialize(session:, url:, inputfile:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
|
@@ -39,6 +71,7 @@ module Bidi2pdf
|
|
39
71
|
|
40
72
|
@window = window
|
41
73
|
@tab = tab
|
74
|
+
@user_context = user_context
|
42
75
|
|
43
76
|
add_cookies(tab)
|
44
77
|
|
@@ -99,15 +132,14 @@ module Bidi2pdf
|
|
99
132
|
|
100
133
|
if @wait_window_loaded
|
101
134
|
Bidi2pdf.logger.info "Waiting for window to be loaded"
|
102
|
-
@tab.
|
103
|
-
new Promise(resolve => { const check = () => window.loaded ? resolve('done') : setTimeout(check, 100); check(); });
|
104
|
-
EOF_SCRIPT
|
135
|
+
@tab.wait_until_page_loaded
|
105
136
|
end
|
106
137
|
|
107
138
|
@tab.print(@output, print_options: @print_options)
|
108
139
|
ensure
|
109
140
|
@tab.close
|
110
141
|
@window.close
|
142
|
+
@user_context.close
|
111
143
|
end
|
112
144
|
|
113
145
|
# rubocop: enable Metrics/AbcSize
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdf
|
4
|
+
class VerboseLogger < SimpleDelegator
|
5
|
+
VERBOSITY_LEVELS = {
|
6
|
+
none: 0,
|
7
|
+
low: 1,
|
8
|
+
medium: 2,
|
9
|
+
high: 3
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
attr_reader :logger, :verbosity
|
13
|
+
|
14
|
+
def initialize(logger, verbosity = :low)
|
15
|
+
super(logger)
|
16
|
+
self.verbosity = verbosity
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def verbosity=(verbosity)
|
21
|
+
min_verbosity = VERBOSITY_LEVELS.values.min
|
22
|
+
|
23
|
+
@verbosity = if verbosity.is_a?(Numeric)
|
24
|
+
verbosity = verbosity.to_i
|
25
|
+
max_verbosity = VERBOSITY_LEVELS.values.max
|
26
|
+
|
27
|
+
verbosity.clamp(min_verbosity, max_verbosity)
|
28
|
+
else
|
29
|
+
VERBOSITY_LEVELS.fetch verbosity.to_sym, min_verbosity
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def verbosity_sym
|
34
|
+
VERBOSITY_LEVELS.find { |_, v| v == verbosity }.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def debug1(progname = nil, &)
|
38
|
+
return unless debug1?
|
39
|
+
|
40
|
+
logger.debug("[D1] #{progname}", &)
|
41
|
+
end
|
42
|
+
|
43
|
+
def debug1?
|
44
|
+
verbosity >= 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def debug1!
|
48
|
+
@verbosity = VERBOSITY_LEVELS[:high]
|
49
|
+
end
|
50
|
+
|
51
|
+
def debug2(progname = nil, &)
|
52
|
+
return unless debug2?
|
53
|
+
|
54
|
+
logger.debug("[D2] #{progname}", &)
|
55
|
+
end
|
56
|
+
|
57
|
+
def debug2?
|
58
|
+
verbosity >= 2
|
59
|
+
end
|
60
|
+
|
61
|
+
def debug2!
|
62
|
+
@verbosity = VERBOSITY_LEVELS[:high]
|
63
|
+
end
|
64
|
+
|
65
|
+
def debug3(progname = nil, &)
|
66
|
+
return unless debug3?
|
67
|
+
|
68
|
+
logger.debug("[D3] #{progname}", &)
|
69
|
+
end
|
70
|
+
|
71
|
+
def debug3?
|
72
|
+
verbosity >= 3
|
73
|
+
end
|
74
|
+
|
75
|
+
def debug3!
|
76
|
+
@verbosity = VERBOSITY_LEVELS[:high]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/bidi2pdf/version.rb
CHANGED
data/lib/bidi2pdf.rb
CHANGED
@@ -1,14 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "bidi2pdf/utils"
|
4
3
|
require_relative "bidi2pdf/process_tree"
|
5
4
|
require_relative "bidi2pdf/launcher"
|
6
5
|
require_relative "bidi2pdf/bidi/session"
|
7
6
|
require_relative "bidi2pdf/dsl"
|
7
|
+
require_relative "bidi2pdf/notifications"
|
8
|
+
require_relative "bidi2pdf/notifications/logging_subscriber"
|
9
|
+
require_relative "bidi2pdf/verbose_logger"
|
8
10
|
|
9
11
|
require "logger"
|
10
12
|
|
11
13
|
module Bidi2pdf
|
14
|
+
PAPER_FORMATS_CM = {
|
15
|
+
letter: { width: 21.59, height: 27.94 },
|
16
|
+
legal: { width: 21.59, height: 35.56 },
|
17
|
+
tabloid: { width: 27.94, height: 43.18 },
|
18
|
+
ledger: { width: 43.18, height: 27.94 },
|
19
|
+
a0: { width: 84.1, height: 118.9 },
|
20
|
+
a1: { width: 59.4, height: 84.1 },
|
21
|
+
a2: { width: 42.0, height: 59.4 },
|
22
|
+
a3: { width: 29.7, height: 42.0 },
|
23
|
+
a4: { width: 21.0, height: 29.7 },
|
24
|
+
a5: { width: 14.8, height: 21.0 },
|
25
|
+
a6: { width: 10.5, height: 14.8 }
|
26
|
+
}.freeze
|
27
|
+
|
12
28
|
class Error < StandardError; end
|
13
29
|
|
14
30
|
class SessionNotStartedError < Error; end
|
@@ -25,19 +41,95 @@ module Bidi2pdf
|
|
25
41
|
|
26
42
|
class PrintError < Error; end
|
27
43
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
44
|
+
class ScriptInjectionError < Error; end
|
45
|
+
|
46
|
+
class StyleInjectionError < Error; end
|
47
|
+
|
48
|
+
class NotificationsError < Error
|
49
|
+
attr_reader :causes
|
32
50
|
|
33
|
-
|
51
|
+
def initialize(causes)
|
52
|
+
@causes = causes
|
53
|
+
exception_class_names = causes.map { |e| e.class.name }
|
54
|
+
super("Notifications errors: #{exception_class_names.join(", ")}")
|
55
|
+
end
|
56
|
+
end
|
34
57
|
|
35
58
|
class << self
|
36
|
-
attr_accessor :
|
59
|
+
attr_accessor :default_timeout, :enable_default_logging_subscriber
|
60
|
+
attr_reader :logging_subscriber, :logger, :network_events_logger, :browser_console_logger, :notification_service
|
37
61
|
|
38
62
|
# Allow configuration through a block
|
39
63
|
def configure
|
40
64
|
yield self if block_given?
|
65
|
+
|
66
|
+
init
|
67
|
+
end
|
68
|
+
|
69
|
+
def init
|
70
|
+
self.logging_subscriber = (Notifications::LoggingSubscriber.new(logger: logger) if enable_default_logging_subscriber)
|
71
|
+
begin
|
72
|
+
require "websocket-native"
|
73
|
+
|
74
|
+
logger.debug "websocket-native available; use enhance performance."
|
75
|
+
rescue LoadError => e
|
76
|
+
raise unless e.message =~ /websocket-native/
|
77
|
+
|
78
|
+
logger.warn "websocket-native not available; installing it may enhance performance."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def translate_paper_format(format)
|
83
|
+
format = format.to_s.downcase.to_sym
|
84
|
+
|
85
|
+
dim = PAPER_FORMATS_CM[format]
|
86
|
+
|
87
|
+
raise ArgumentError, "Invalid paper format: #{format}" unless dim
|
88
|
+
|
89
|
+
width = dim[:width] || 0
|
90
|
+
height = dim[:height] || 0
|
91
|
+
|
92
|
+
{ width: width, height: height }
|
93
|
+
end
|
94
|
+
|
95
|
+
def logger=(new_logger)
|
96
|
+
@logger = Bidi2pdf::VerboseLogger.new new_logger
|
97
|
+
end
|
98
|
+
|
99
|
+
def network_events_logger=(new_network_events_logger)
|
100
|
+
@network_events_logger = Bidi2pdf::VerboseLogger.new(new_network_events_logger)
|
101
|
+
end
|
102
|
+
|
103
|
+
def browser_console_logger=(new_browser_console_logger)
|
104
|
+
@browser_console_logger = Bidi2pdf::VerboseLogger.new(new_browser_console_logger)
|
41
105
|
end
|
106
|
+
|
107
|
+
def logging_subscriber=(new_logging_subscriber)
|
108
|
+
@logging_subscriber&.unsubscribe
|
109
|
+
@logging_subscriber = new_logging_subscriber
|
110
|
+
end
|
111
|
+
|
112
|
+
def notification_service=(new_notification_service)
|
113
|
+
@logging_subscriber&.unsubscribe
|
114
|
+
|
115
|
+
@notification_service = new_notification_service
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
configure do |config|
|
120
|
+
config.logger = Logger.new($stdout)
|
121
|
+
config.logger.level = Logger::INFO
|
122
|
+
|
123
|
+
config.network_events_logger = Logger.new($stdout)
|
124
|
+
config.network_events_logger.level = Logger::FATAL
|
125
|
+
|
126
|
+
config.browser_console_logger = Logger.new($stdout)
|
127
|
+
config.browser_console_logger.level = Logger::WARN
|
128
|
+
|
129
|
+
config.enable_default_logging_subscriber = true
|
130
|
+
|
131
|
+
config.default_timeout = 60
|
132
|
+
|
133
|
+
config.notification_service = Notifications
|
42
134
|
end
|
43
135
|
end
|
@@ -34,7 +34,7 @@ module Bidi2pdf
|
|
34
34
|
|
35
35
|
def wait_until_open: (?timeout: untyped) -> untyped
|
36
36
|
|
37
|
-
def send_cmd: (
|
37
|
+
def send_cmd: (Bidi2pdf::Bidi::Commands::Base cmd) -> untyped
|
38
38
|
|
39
39
|
# rubocop:disable Metrics/AbcSize
|
40
40
|
def send_cmd_and_wait: (untyped method, ?::Hash[untyped, untyped] params, ?timeout: untyped) ?{ (untyped) -> untyped } -> untyped
|