bidi2pdf 0.1.3 → 0.1.5
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 +19 -1
- data/CHANGELOG.md +40 -3
- data/README.md +145 -55
- data/docker/Dockerfile.chromedriver +5 -0
- data/docker/entrypoint.sh +11 -1
- data/lib/bidi2pdf/bidi/add_headers_interceptor.rb +18 -21
- data/lib/bidi2pdf/bidi/auth_interceptor.rb +31 -38
- data/lib/bidi2pdf/bidi/browser_tab.rb +47 -53
- data/lib/bidi2pdf/bidi/client.rb +24 -52
- data/lib/bidi2pdf/bidi/command_manager.rb +50 -28
- data/lib/bidi2pdf/bidi/commands/add_intercept.rb +41 -0
- data/lib/bidi2pdf/bidi/commands/base.rb +73 -0
- data/lib/bidi2pdf/bidi/commands/browser_close.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/browser_create_user_context.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_close.rb +25 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_navigate.rb +31 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +28 -0
- data/lib/bidi2pdf/bidi/commands/cancel_auth.rb +26 -0
- data/lib/bidi2pdf/bidi/commands/create_tab.rb +11 -0
- data/lib/bidi2pdf/bidi/commands/create_window.rb +32 -0
- data/lib/bidi2pdf/bidi/commands/get_user_contexts.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/network_continue.rb +29 -0
- data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +116 -0
- data/lib/bidi2pdf/bidi/commands/provide_credentials.rb +33 -0
- data/lib/bidi2pdf/bidi/commands/script_evaluate.rb +33 -0
- data/lib/bidi2pdf/bidi/commands/session_end.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/session_status.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/session_subscribe.rb +25 -0
- data/lib/bidi2pdf/bidi/commands/set_tab_cookie.rb +71 -0
- data/lib/bidi2pdf/bidi/commands/set_usercontext_cookie.rb +67 -0
- data/lib/bidi2pdf/bidi/commands.rb +27 -0
- data/lib/bidi2pdf/bidi/connection_manager.rb +16 -13
- data/lib/bidi2pdf/bidi/event_manager.rb +2 -0
- data/lib/bidi2pdf/bidi/interceptor.rb +75 -0
- data/lib/bidi2pdf/bidi/network_events.rb +0 -1
- data/lib/bidi2pdf/bidi/session.rb +139 -65
- data/lib/bidi2pdf/bidi/user_context.rb +25 -31
- data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +2 -0
- data/lib/bidi2pdf/chromedriver_manager.rb +2 -1
- data/lib/bidi2pdf/cli.rb +12 -7
- data/lib/bidi2pdf/dsl.rb +45 -0
- data/lib/bidi2pdf/launcher.rb +6 -2
- data/lib/bidi2pdf/session_runner.rb +9 -2
- data/lib/bidi2pdf/version.rb +1 -1
- data/lib/bidi2pdf.rb +16 -1
- data/sig/bidi2pdf/bidi/command_manager.rbs +41 -0
- data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +21 -0
- data/sig/bidi2pdf/bidi/commands/base.rbs +27 -0
- data/sig/bidi2pdf/bidi/commands/browser_close.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +11 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +14 -0
- data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +11 -0
- data/sig/bidi2pdf/bidi/commands/create_tab.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/create_window.rbs +19 -0
- data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/network_continue.rbs +19 -0
- data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +53 -0
- data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +17 -0
- data/sig/bidi2pdf/bidi/commands/session_end.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/session_status.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +31 -0
- data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +27 -0
- data/sig/bidi2pdf/bidi/commands.rbs +6 -0
- data/sig/bidi2pdf/bidi/connection_manager.rbs +17 -0
- data/sig/bidi2pdf/bidi/interceptor.rbs +31 -0
- data/tasks/coverage.rake +16 -0
- metadata +66 -11
- data/lib/bidi2pdf/bidi/print_parameters_validator.rb +0 -114
- data/sig/bidi2pdf/bidi/print_parameters_validator.rbs +0 -44
- data/sig/bidi2pdf/chrome/chromedriver_downloader.rbs +0 -11
- data/sig/bidi2pdf/chrome/downloader_helper.rbs +0 -9
- data/sig/bidi2pdf/chrome/finder.rbs +0 -27
- data/sig/bidi2pdf/chrome/platform.rbs +0 -13
- data/sig/bidi2pdf/chrome/version_resolver.rbs +0 -19
@@ -10,134 +10,208 @@ require_relative "user_context"
|
|
10
10
|
module Bidi2pdf
|
11
11
|
module Bidi
|
12
12
|
class Session
|
13
|
-
SUBSCRIBE_EVENTS = %w[
|
14
|
-
|
15
|
-
script
|
16
|
-
].freeze
|
13
|
+
SUBSCRIBE_EVENTS = %w[log script].freeze
|
14
|
+
DEFAULT_CHROME_ARGS = %w[--disable-gpu --disable-popup-blocking --disable-hang-monitor].freeze
|
17
15
|
|
18
|
-
attr_reader :session_uri, :started
|
16
|
+
attr_reader :session_uri, :started, :chrome_args
|
19
17
|
|
20
|
-
def initialize(session_url:, headless: true)
|
18
|
+
def initialize(session_url:, headless: true, chrome_args: DEFAULT_CHROME_ARGS)
|
21
19
|
@session_uri = URI(session_url)
|
22
20
|
@headless = headless
|
23
|
-
@client = nil
|
24
|
-
@browser = nil
|
25
|
-
@websocket_url = nil
|
26
21
|
@started = false
|
22
|
+
@chrome_args = chrome_args.dup
|
27
23
|
end
|
28
24
|
|
29
25
|
def start
|
26
|
+
return if started?
|
27
|
+
|
30
28
|
@started = true
|
31
29
|
client
|
30
|
+
rescue StandardError => e
|
31
|
+
@started = false
|
32
|
+
raise e
|
32
33
|
end
|
33
34
|
|
34
35
|
def client
|
35
|
-
@client ||=
|
36
|
-
end
|
37
|
-
|
38
|
-
def close
|
39
|
-
client&.send_cmd_and_wait("session.end", {}) do |response|
|
40
|
-
Bidi2pdf.logger.debug "Session ended: #{response}"
|
41
|
-
@client&.close
|
42
|
-
@client = nil
|
43
|
-
@websocket_url = nil
|
44
|
-
@browser = nil
|
45
|
-
end
|
36
|
+
@client ||= started? ? create_client : nil
|
46
37
|
end
|
47
38
|
|
48
39
|
def browser
|
49
40
|
@browser ||= create_browser
|
50
41
|
end
|
51
42
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
43
|
+
def close
|
44
|
+
return unless started?
|
45
|
+
|
46
|
+
2.times do |attempt|
|
47
|
+
client&.send_cmd_and_wait(Bidi2pdf::Bidi::Commands::SessionEnd.new, timeout: 1) do |response|
|
48
|
+
Bidi2pdf.logger.info "Session ended: #{response}"
|
49
|
+
|
50
|
+
cleanup
|
51
|
+
end
|
52
|
+
break
|
53
|
+
rescue CmdTimeoutError
|
54
|
+
Bidi2pdf.logger.error "Session end command timed out. Retrying... (#{attempt + 1})"
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
def user_contexts
|
59
|
+
send_cmd(Bidi2pdf::Bidi::Commands::GetUserContexts.new) { |resp| Bidi2pdf.logger.debug "User contexts: #{resp}" }
|
60
|
+
end
|
61
|
+
|
58
62
|
def status
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
send_cmd(Bidi2pdf::Bidi::Commands::SessionStatus.new) { |resp| Bidi2pdf.logger.info "Session status: #{resp.inspect}" }
|
64
|
+
end
|
65
|
+
|
66
|
+
def started?
|
67
|
+
@started
|
68
|
+
end
|
69
|
+
|
70
|
+
def websocket_url
|
71
|
+
return @websocket_url if @websocket_url
|
72
|
+
|
73
|
+
@websocket_url = if %w[ws wss].include?(session_uri.scheme)
|
74
|
+
session_uri.to_s
|
75
|
+
else
|
76
|
+
create_new_session
|
77
|
+
end
|
62
78
|
end
|
63
79
|
|
64
80
|
private
|
65
81
|
|
82
|
+
def send_cmd(command, &block)
|
83
|
+
client&.send_cmd_and_wait(command, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# rubocop: disable Metrics/AbcSize
|
66
87
|
def create_browser
|
67
88
|
start
|
68
|
-
|
69
|
-
|
70
|
-
@client.wait_until_open
|
89
|
+
client.start
|
90
|
+
client.wait_until_open
|
71
91
|
|
72
92
|
Bidi2pdf.logger.info "Subscribing to events"
|
73
93
|
|
74
|
-
|
75
|
-
|
94
|
+
Bidi::Client.new(websocket_url).tap do |event_client|
|
95
|
+
event_client.start
|
96
|
+
event_client.wait_until_open
|
76
97
|
|
77
|
-
|
78
|
-
|
98
|
+
event_client.on_event(*SUBSCRIBE_EVENTS) do |data|
|
99
|
+
Bidi2pdf.logger.debug "Received event: #{data["method"]}, params: #{data["params"]}"
|
100
|
+
end
|
79
101
|
end
|
80
102
|
|
81
|
-
Bidi::Browser.new(
|
103
|
+
Bidi::Browser.new(client)
|
82
104
|
end
|
83
105
|
|
106
|
+
# rubocop: enable Metrics/AbcSize
|
107
|
+
|
84
108
|
def create_client
|
85
109
|
Bidi::Client.new(websocket_url).tap(&:start)
|
86
110
|
end
|
87
111
|
|
88
|
-
|
89
|
-
|
90
|
-
|
112
|
+
def create_new_session
|
113
|
+
session_data = exec_api_call(session_request)
|
114
|
+
Bidi2pdf.logger.debug "Session data: #{session_data}"
|
91
115
|
|
92
|
-
|
93
|
-
|
94
|
-
return @websocket_url
|
95
|
-
end
|
116
|
+
value = session_data["value"]
|
117
|
+
handle_error(value) if value.nil? || value["error"]
|
96
118
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
119
|
+
session_id = value["sessionId"]
|
120
|
+
ws_url = value["capabilities"]["webSocketUrl"]
|
121
|
+
|
122
|
+
Bidi2pdf.logger.info "Created session with ID: #{session_id}"
|
123
|
+
Bidi2pdf.logger.info "WebSocket URL: #{ws_url}"
|
124
|
+
ws_url
|
125
|
+
end
|
102
126
|
|
103
|
-
|
127
|
+
def session_request
|
128
|
+
session_chrome_args = chrome_args.dup
|
129
|
+
session_chrome_args << "--headless" if @headless
|
104
130
|
|
105
|
-
|
131
|
+
{
|
106
132
|
"capabilities" => {
|
107
133
|
"alwaysMatch" => {
|
108
134
|
"browserName" => "chrome",
|
109
|
-
"goog:chromeOptions" => {
|
110
|
-
"args" => args
|
111
|
-
},
|
135
|
+
"goog:chromeOptions" => { "args" => session_chrome_args },
|
112
136
|
"goog:prerenderingDisabled" => true,
|
113
|
-
"unhandledPromptBehavior" => {
|
114
|
-
default: "ignore"
|
115
|
-
},
|
137
|
+
"unhandledPromptBehavior" => { default: "ignore" },
|
116
138
|
"acceptInsecureCerts" => true,
|
117
139
|
"webSocketUrl" => true
|
118
140
|
}
|
119
141
|
}
|
120
142
|
}
|
143
|
+
end
|
121
144
|
|
122
|
-
|
145
|
+
def exec_api_call(payload)
|
146
|
+
response = Net::HTTP.post(session_uri, payload.to_json, "Content-Type" => "application/json")
|
147
|
+
body = response.body
|
148
|
+
code = response.code.to_i
|
123
149
|
|
124
|
-
|
125
|
-
|
150
|
+
if code != 200
|
151
|
+
log_api_error("Failed to create session", code, body)
|
152
|
+
return build_error("Session creation failed", "Response code: #{code}")
|
153
|
+
end
|
126
154
|
|
127
|
-
|
155
|
+
JSON.parse(body)
|
156
|
+
rescue StandardError => e
|
157
|
+
error_type = error_category(e)
|
158
|
+
build_error(error_type, "#{error_description(error_type)} #{e.message}", e.backtrace)
|
159
|
+
end
|
128
160
|
|
129
|
-
|
161
|
+
def log_api_error(message, code, body)
|
162
|
+
Bidi2pdf.logger.error "#{message}. Response code: #{code}"
|
163
|
+
Bidi2pdf.logger.error "Response body: #{body}"
|
164
|
+
end
|
130
165
|
|
131
|
-
|
132
|
-
|
166
|
+
def error_category(exception)
|
167
|
+
case exception
|
168
|
+
when Errno::ECONNREFUSED then "Connection refused"
|
169
|
+
when JSON::ParserError then "Invalid JSON response"
|
170
|
+
else "Unknown error"
|
171
|
+
end
|
172
|
+
end
|
133
173
|
|
134
|
-
|
135
|
-
|
174
|
+
def error_description(type)
|
175
|
+
{
|
176
|
+
"Connection refused" => "Could not connect to the session URL:",
|
177
|
+
"Invalid JSON response" => "Could not parse the response:",
|
178
|
+
"Unknown error" => "An unknown error occurred:"
|
179
|
+
}[type]
|
180
|
+
end
|
181
|
+
|
182
|
+
def build_error(error, message, backtrace = nil)
|
183
|
+
{
|
184
|
+
"value" => {
|
185
|
+
"error" => error,
|
186
|
+
"message" => message,
|
187
|
+
"stacktrace" => backtrace&.join("\n")
|
188
|
+
}.compact
|
189
|
+
}
|
190
|
+
end
|
136
191
|
|
137
|
-
|
192
|
+
def handle_error(value)
|
193
|
+
error = value["error"]
|
194
|
+
return unless error
|
195
|
+
|
196
|
+
msg = value["message"]
|
197
|
+
trace = value["stacktrace"]
|
198
|
+
|
199
|
+
Bidi2pdf.logger.error "Error: #{error} message: #{msg}"
|
200
|
+
Bidi2pdf.logger.error "Stacktrace: #{trace}" if trace
|
201
|
+
|
202
|
+
if msg =~ /probably user data directory is already in use/
|
203
|
+
Bidi2pdf.logger.info "Container detected with headless-only support, ensure xvfb is started" unless @headless
|
204
|
+
Bidi2pdf.logger.info "Check chromedriver permissions and --user-data-dir"
|
205
|
+
end
|
206
|
+
|
207
|
+
raise SessionNotStartedError,
|
208
|
+
"Session not started. Check logs for more details. Error: #{error} message: #{msg}"
|
138
209
|
end
|
139
210
|
|
140
|
-
|
211
|
+
def cleanup
|
212
|
+
@client&.close
|
213
|
+
@client = @websocket_url = @browser = nil
|
214
|
+
end
|
141
215
|
end
|
142
216
|
end
|
143
217
|
end
|
@@ -14,14 +14,16 @@ module Bidi2pdf
|
|
14
14
|
|
15
15
|
def context_id
|
16
16
|
@context_id ||= begin
|
17
|
-
|
18
|
-
|
19
|
-
end
|
17
|
+
res = client.send_cmd_and_wait(Bidi2pdf::Bidi::Commands::BrowserCreateUserContext.new) do |response|
|
18
|
+
raise "Error creating user context: #{response.inspect}" if response["error"]
|
20
19
|
|
21
|
-
|
20
|
+
response["result"]["userContext"]
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
Bidi2pdf.logger.debug "User context created: #{res.inspect}"
|
24
|
+
|
25
|
+
res
|
26
|
+
end
|
25
27
|
end
|
26
28
|
|
27
29
|
def set_cookie(
|
@@ -35,36 +37,28 @@ module Bidi2pdf
|
|
35
37
|
same_site: "strict",
|
36
38
|
ttl: 30
|
37
39
|
)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
},
|
53
|
-
partition: {
|
54
|
-
type: "storageKey",
|
55
|
-
userContext: context_id,
|
56
|
-
sourceOrigin: source_origin
|
57
|
-
}
|
58
|
-
}) do |response|
|
40
|
+
cmd = Bidi2pdf::Bidi::Commands::SetUsercontextCookie.new(
|
41
|
+
user_context_id: context_id,
|
42
|
+
source_origin: source_origin,
|
43
|
+
name: name,
|
44
|
+
value: value,
|
45
|
+
domain: domain,
|
46
|
+
path: path,
|
47
|
+
secure: secure,
|
48
|
+
http_only: http_only,
|
49
|
+
same_site: same_site,
|
50
|
+
ttl: ttl
|
51
|
+
)
|
52
|
+
|
53
|
+
client.send_cmd_and_wait(cmd) do |response|
|
59
54
|
Bidi2pdf.logger.debug "Cookie set: #{response.inspect}"
|
60
55
|
end
|
61
56
|
end
|
62
57
|
|
63
58
|
def create_browser_window
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
}) do |response|
|
59
|
+
cmd = Bidi2pdf::Bidi::Commands::CreateWindow.new(user_context_id: context_id)
|
60
|
+
|
61
|
+
client.send_cmd_and_wait(cmd) do |response|
|
68
62
|
browsing_context_id = response["result"]["context"]
|
69
63
|
|
70
64
|
BrowserTab.new(client, browsing_context_id, context_id)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "chromedriver/binary"
|
4
|
+
require "securerandom"
|
4
5
|
|
5
6
|
module Bidi2pdf
|
6
7
|
class ChromedriverManager
|
@@ -140,7 +141,7 @@ module Bidi2pdf
|
|
140
141
|
|
141
142
|
def build_cmd
|
142
143
|
bin = Chromedriver::Binary::ChromedriverDownloader.driver_path
|
143
|
-
user_data_dir = File.join(Dir.tmpdir, "bidi2pdf", "user_data")
|
144
|
+
user_data_dir = File.join(Dir.tmpdir, "bidi2pdf", "user_data", SecureRandom.hex(8))
|
144
145
|
|
145
146
|
cmd = [bin]
|
146
147
|
cmd << "--port=#{@port}" unless @port.zero?
|
data/lib/bidi2pdf/cli.rb
CHANGED
@@ -31,6 +31,10 @@ module Bidi2pdf
|
|
31
31
|
USAGE
|
32
32
|
|
33
33
|
option :url, desc: "The URL to render"
|
34
|
+
# of course, it's possible to render a local file via: --url file:///path/to/the/file.html
|
35
|
+
# but this should showcase the scenario that you render a string within ruby as pdf without the need
|
36
|
+
# to store it on disc
|
37
|
+
option :html_file, desc: "The local HTML file to render"
|
34
38
|
option :output, default: "output.pdf", desc: "Filename for the output PDF", aliases: "-o"
|
35
39
|
option :cookie, type: :array, default: [], banner: "name=value", desc: "One or more cookies", aliases: "-C"
|
36
40
|
option :header, type: :array, default: [], banner: "name=value", desc: "One or more custom headers", aliases: "-H"
|
@@ -63,7 +67,7 @@ module Bidi2pdf
|
|
63
67
|
def render
|
64
68
|
load_config
|
65
69
|
|
66
|
-
validate_required_options!
|
70
|
+
validate_required_options!
|
67
71
|
|
68
72
|
configure
|
69
73
|
|
@@ -115,14 +119,16 @@ module Bidi2pdf
|
|
115
119
|
YAML.load_file(options[:config]).transform_keys(&:to_sym)
|
116
120
|
end
|
117
121
|
|
118
|
-
def validate_required_options!
|
119
|
-
|
120
|
-
raise Thor::Error, "Missing required option
|
122
|
+
def validate_required_options!
|
123
|
+
if merged_options[:url].nil? && merged_options[:html_file].nil?
|
124
|
+
raise Thor::Error, "Missing required option --url or --html_file needs to be specified"
|
125
|
+
elsif merged_options[:html_file] && !File.readable?(merged_options[:html_file])
|
126
|
+
raise Thor::Error, "HTML file '#{merged_options[:html_file]}' not found or not readable"
|
121
127
|
end
|
122
128
|
end
|
123
129
|
|
124
130
|
def validate_print_options(opts)
|
125
|
-
Bidi2pdf::Bidi::PrintParametersValidator.validate!(opts)
|
131
|
+
Bidi2pdf::Bidi::Commands::PrintParametersValidator.validate!(opts)
|
126
132
|
rescue ArgumentError => e
|
127
133
|
raise Thor::Error, "Invalid print option: #{e.message}"
|
128
134
|
end
|
@@ -177,12 +183,12 @@ module Bidi2pdf
|
|
177
183
|
end
|
178
184
|
|
179
185
|
def launcher
|
180
|
-
# rubocop:disable Layout/BeginEndAlignment
|
181
186
|
@launcher ||= begin
|
182
187
|
username, password = parse_auth(merged_options[:auth]) if merged_options[:auth]
|
183
188
|
|
184
189
|
Bidi2pdf::Launcher.new(
|
185
190
|
url: merged_options[:url],
|
191
|
+
inputfile: merged_options[:hmtl_file],
|
186
192
|
output: merged_options[:output],
|
187
193
|
cookies: parse_key_values(merged_options[:cookie]),
|
188
194
|
headers: parse_key_values(merged_options[:header]),
|
@@ -195,7 +201,6 @@ module Bidi2pdf
|
|
195
201
|
print_options: print_options
|
196
202
|
)
|
197
203
|
end
|
198
|
-
# rubocop:enable Layout/BeginEndAlignment
|
199
204
|
end
|
200
205
|
|
201
206
|
# rubocop:enable Metrics/AbcSize
|
data/lib/bidi2pdf/dsl.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bidi2pdf"
|
4
|
+
|
5
|
+
module Bidi2pdf
|
6
|
+
module DSL
|
7
|
+
# rubocop: disable Metrics/AbcSize
|
8
|
+
def self.with_tab(remote_browser_url: nil, port: 0, headless: true, chrome_args: Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup)
|
9
|
+
manager = nil
|
10
|
+
session = nil
|
11
|
+
tab = nil
|
12
|
+
|
13
|
+
begin
|
14
|
+
session = if remote_browser_url
|
15
|
+
Bidi2pdf::Bidi::Session.new(
|
16
|
+
session_url: remote_browser_url,
|
17
|
+
headless: true, # remote is always headless
|
18
|
+
chrome_args: chrome_args
|
19
|
+
)
|
20
|
+
else
|
21
|
+
manager = Bidi2pdf::ChromedriverManager.new(port: port, headless: headless)
|
22
|
+
manager.start
|
23
|
+
manager.session
|
24
|
+
end
|
25
|
+
|
26
|
+
session.start
|
27
|
+
session.client.on_close { Bidi2pdf.logger.info "WebSocket session closed" }
|
28
|
+
|
29
|
+
browser = session.browser
|
30
|
+
context = browser.create_user_context
|
31
|
+
window = context.create_browser_window
|
32
|
+
tab = window.create_browser_tab
|
33
|
+
|
34
|
+
yield(tab)
|
35
|
+
ensure
|
36
|
+
tab&.close
|
37
|
+
window&.close
|
38
|
+
session&.close
|
39
|
+
manager&.stop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop: enable Metrics/AbcSize
|
44
|
+
end
|
45
|
+
end
|
data/lib/bidi2pdf/launcher.rb
CHANGED
@@ -7,9 +7,10 @@ require_relative "bidi/session"
|
|
7
7
|
module Bidi2pdf
|
8
8
|
class Launcher
|
9
9
|
# rubocop:disable Metrics/ParameterLists
|
10
|
-
def initialize(url:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
|
10
|
+
def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
|
11
11
|
wait_network_idle: false, print_options: {}, remote_browser_url: nil)
|
12
12
|
@url = url
|
13
|
+
@inputfile = inputfile
|
13
14
|
@port = port
|
14
15
|
@headless = headless
|
15
16
|
@output = output
|
@@ -21,6 +22,7 @@ module Bidi2pdf
|
|
21
22
|
@wait_network_idle = wait_network_idle
|
22
23
|
@print_options = print_options || {}
|
23
24
|
@remote_browser_url = remote_browser_url
|
25
|
+
@custom_session = nil
|
24
26
|
end
|
25
27
|
|
26
28
|
# rubocop:enable Metrics/ParameterLists
|
@@ -29,6 +31,7 @@ module Bidi2pdf
|
|
29
31
|
runner = SessionRunner.new(
|
30
32
|
session: session,
|
31
33
|
url: @url,
|
34
|
+
inputfile: @inputfile,
|
32
35
|
output: @output,
|
33
36
|
cookies: @cookies,
|
34
37
|
headers: @headers,
|
@@ -42,13 +45,14 @@ module Bidi2pdf
|
|
42
45
|
|
43
46
|
def stop
|
44
47
|
@manager&.stop
|
48
|
+
@custom_session&.close
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
48
52
|
|
49
53
|
def session
|
50
54
|
if @remote_browser_url
|
51
|
-
Bidi::Session.new(session_url: @remote_browser_url, headless: @headless)
|
55
|
+
@custom_session = Bidi::Session.new(session_url: @remote_browser_url, headless: @headless)
|
52
56
|
else
|
53
57
|
@manager = ChromedriverManager.new(port: @port, headless: @headless)
|
54
58
|
@manager.start
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module Bidi2pdf
|
4
4
|
class SessionRunner
|
5
|
-
def initialize(session:, url:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
|
5
|
+
def initialize(session:, url:, inputfile:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
|
6
6
|
wait_network_idle: false, print_options: {})
|
7
7
|
@session = session
|
8
8
|
@url = url
|
9
|
+
@inputfile = inputfile
|
9
10
|
@output = output
|
10
11
|
@cookies = cookies || {}
|
11
12
|
@headers = headers || {}
|
@@ -75,7 +76,13 @@ module Bidi2pdf
|
|
75
76
|
@session.status
|
76
77
|
@session.user_contexts
|
77
78
|
|
78
|
-
@
|
79
|
+
if @url
|
80
|
+
@tab.open_page(@url)
|
81
|
+
else
|
82
|
+
Bidi2pdf.logger.info "Loading HTML file #{@inputfile}"
|
83
|
+
data = File.read(@inputfile)
|
84
|
+
@tab.view_html_page(data)
|
85
|
+
end
|
79
86
|
|
80
87
|
if @wait_network_idle
|
81
88
|
Bidi2pdf.logger.info "Waiting for network idle"
|
data/lib/bidi2pdf/version.rb
CHANGED
data/lib/bidi2pdf.rb
CHANGED
@@ -4,14 +4,29 @@ require_relative "bidi2pdf/utils"
|
|
4
4
|
require_relative "bidi2pdf/process_tree"
|
5
5
|
require_relative "bidi2pdf/launcher"
|
6
6
|
require_relative "bidi2pdf/bidi/session"
|
7
|
+
require_relative "bidi2pdf/dsl"
|
7
8
|
|
8
9
|
require "logger"
|
9
10
|
|
10
11
|
module Bidi2pdf
|
11
12
|
class Error < StandardError; end
|
12
13
|
|
14
|
+
class SessionNotStartedError < Error; end
|
15
|
+
|
16
|
+
class WebsocketError < Error; end
|
17
|
+
|
18
|
+
class ClientError < WebsocketError; end
|
19
|
+
|
20
|
+
class CmdError < ClientError; end
|
21
|
+
|
22
|
+
class CmdResponseNotStoredError < ClientError; end
|
23
|
+
|
24
|
+
class CmdTimeoutError < ClientError; end
|
25
|
+
|
26
|
+
class PrintError < Error; end
|
27
|
+
|
13
28
|
@logger = Logger.new($stdout)
|
14
|
-
@logger.level = Logger::
|
29
|
+
@logger.level = Logger::INFO
|
15
30
|
|
16
31
|
@default_timeout = 60
|
17
32
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Bidi2pdf
|
2
|
+
module Bidi
|
3
|
+
class CommandManager
|
4
|
+
self.@id: untyped
|
5
|
+
|
6
|
+
self.@id_mutex: untyped
|
7
|
+
|
8
|
+
@socket: untyped
|
9
|
+
|
10
|
+
@logger: untyped
|
11
|
+
|
12
|
+
@pending_responses: untyped
|
13
|
+
|
14
|
+
@initiated_cmds: untyped
|
15
|
+
|
16
|
+
def self.initialize_counter: () -> untyped
|
17
|
+
|
18
|
+
def self.next_id: () -> untyped
|
19
|
+
|
20
|
+
def initialize: (untyped socket, logger: untyped) -> void
|
21
|
+
|
22
|
+
def send_cmd: (untyped cmd, ?store_response: bool) -> untyped
|
23
|
+
|
24
|
+
def send_cmd_and_wait: (untyped cmd, ?timeout: untyped) ?{ (untyped) -> untyped } -> untyped
|
25
|
+
|
26
|
+
def pop_response: (untyped id, timeout: untyped) -> untyped
|
27
|
+
|
28
|
+
def handle_response: (untyped data) -> (true | untyped | false)
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def init_queue_for: (untyped id) -> untyped
|
33
|
+
|
34
|
+
def next_id: () -> untyped
|
35
|
+
|
36
|
+
def redact_sensitive_fields: (untyped obj, ?::Array[untyped] sensitive_keys) -> untyped
|
37
|
+
|
38
|
+
def raise_timeout_error: (untyped id, untyped cmd) -> untyped
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bidi2pdf
|
2
|
+
module Bidi
|
3
|
+
module Commands
|
4
|
+
class AddIntercept
|
5
|
+
include Base
|
6
|
+
|
7
|
+
BEFORE_REQUEST: String
|
8
|
+
RESPONSE_STARTED: String
|
9
|
+
AUTH_REQUIRED: String
|
10
|
+
|
11
|
+
@context: String
|
12
|
+
@phases: Array[String]
|
13
|
+
@url_patterns: Array[String]
|
14
|
+
|
15
|
+
def initialize: (context: String, phases: Array[String], url_patterns: Array[String]) -> void
|
16
|
+
|
17
|
+
def validate_phases!: () -> void
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|