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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -1
  3. data/CHANGELOG.md +40 -3
  4. data/README.md +145 -55
  5. data/docker/Dockerfile.chromedriver +5 -0
  6. data/docker/entrypoint.sh +11 -1
  7. data/lib/bidi2pdf/bidi/add_headers_interceptor.rb +18 -21
  8. data/lib/bidi2pdf/bidi/auth_interceptor.rb +31 -38
  9. data/lib/bidi2pdf/bidi/browser_tab.rb +47 -53
  10. data/lib/bidi2pdf/bidi/client.rb +24 -52
  11. data/lib/bidi2pdf/bidi/command_manager.rb +50 -28
  12. data/lib/bidi2pdf/bidi/commands/add_intercept.rb +41 -0
  13. data/lib/bidi2pdf/bidi/commands/base.rb +73 -0
  14. data/lib/bidi2pdf/bidi/commands/browser_close.rb +15 -0
  15. data/lib/bidi2pdf/bidi/commands/browser_create_user_context.rb +15 -0
  16. data/lib/bidi2pdf/bidi/commands/browsing_context_close.rb +25 -0
  17. data/lib/bidi2pdf/bidi/commands/browsing_context_navigate.rb +31 -0
  18. data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +28 -0
  19. data/lib/bidi2pdf/bidi/commands/cancel_auth.rb +26 -0
  20. data/lib/bidi2pdf/bidi/commands/create_tab.rb +11 -0
  21. data/lib/bidi2pdf/bidi/commands/create_window.rb +32 -0
  22. data/lib/bidi2pdf/bidi/commands/get_user_contexts.rb +15 -0
  23. data/lib/bidi2pdf/bidi/commands/network_continue.rb +29 -0
  24. data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +116 -0
  25. data/lib/bidi2pdf/bidi/commands/provide_credentials.rb +33 -0
  26. data/lib/bidi2pdf/bidi/commands/script_evaluate.rb +33 -0
  27. data/lib/bidi2pdf/bidi/commands/session_end.rb +15 -0
  28. data/lib/bidi2pdf/bidi/commands/session_status.rb +15 -0
  29. data/lib/bidi2pdf/bidi/commands/session_subscribe.rb +25 -0
  30. data/lib/bidi2pdf/bidi/commands/set_tab_cookie.rb +71 -0
  31. data/lib/bidi2pdf/bidi/commands/set_usercontext_cookie.rb +67 -0
  32. data/lib/bidi2pdf/bidi/commands.rb +27 -0
  33. data/lib/bidi2pdf/bidi/connection_manager.rb +16 -13
  34. data/lib/bidi2pdf/bidi/event_manager.rb +2 -0
  35. data/lib/bidi2pdf/bidi/interceptor.rb +75 -0
  36. data/lib/bidi2pdf/bidi/network_events.rb +0 -1
  37. data/lib/bidi2pdf/bidi/session.rb +139 -65
  38. data/lib/bidi2pdf/bidi/user_context.rb +25 -31
  39. data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +2 -0
  40. data/lib/bidi2pdf/chromedriver_manager.rb +2 -1
  41. data/lib/bidi2pdf/cli.rb +12 -7
  42. data/lib/bidi2pdf/dsl.rb +45 -0
  43. data/lib/bidi2pdf/launcher.rb +6 -2
  44. data/lib/bidi2pdf/session_runner.rb +9 -2
  45. data/lib/bidi2pdf/version.rb +1 -1
  46. data/lib/bidi2pdf.rb +16 -1
  47. data/sig/bidi2pdf/bidi/command_manager.rbs +41 -0
  48. data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +21 -0
  49. data/sig/bidi2pdf/bidi/commands/base.rbs +27 -0
  50. data/sig/bidi2pdf/bidi/commands/browser_close.rbs +9 -0
  51. data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +9 -0
  52. data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +11 -0
  53. data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +15 -0
  54. data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +14 -0
  55. data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +11 -0
  56. data/sig/bidi2pdf/bidi/commands/create_tab.rbs +9 -0
  57. data/sig/bidi2pdf/bidi/commands/create_window.rbs +19 -0
  58. data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +9 -0
  59. data/sig/bidi2pdf/bidi/commands/network_continue.rbs +19 -0
  60. data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +53 -0
  61. data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +15 -0
  62. data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +17 -0
  63. data/sig/bidi2pdf/bidi/commands/session_end.rbs +9 -0
  64. data/sig/bidi2pdf/bidi/commands/session_status.rbs +9 -0
  65. data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +15 -0
  66. data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +31 -0
  67. data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +27 -0
  68. data/sig/bidi2pdf/bidi/commands.rbs +6 -0
  69. data/sig/bidi2pdf/bidi/connection_manager.rbs +17 -0
  70. data/sig/bidi2pdf/bidi/interceptor.rbs +31 -0
  71. data/tasks/coverage.rake +16 -0
  72. metadata +66 -11
  73. data/lib/bidi2pdf/bidi/print_parameters_validator.rb +0 -114
  74. data/sig/bidi2pdf/bidi/print_parameters_validator.rbs +0 -44
  75. data/sig/bidi2pdf/chrome/chromedriver_downloader.rbs +0 -11
  76. data/sig/bidi2pdf/chrome/downloader_helper.rbs +0 -9
  77. data/sig/bidi2pdf/chrome/finder.rbs +0 -27
  78. data/sig/bidi2pdf/chrome/platform.rbs +0 -13
  79. 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
- log
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 ||= @started ? create_client : nil
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 user_contexts
53
- client&.send_cmd_and_wait("browser.getUserContexts", {}) do |response|
54
- Bidi2pdf.logger.debug "User contexts: #{response}"
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
- client&.send_cmd_and_wait("session.status", {}) do |response|
60
- Bidi2pdf.logger.info "Session status: #{response.inspect}"
61
- end
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
- @client.start
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
- event_client = Bidi::Client.new(websocket_url).tap(&:start)
75
- event_client.wait_until_open
94
+ Bidi::Client.new(websocket_url).tap do |event_client|
95
+ event_client.start
96
+ event_client.wait_until_open
76
97
 
77
- event_client.on_event(*SUBSCRIBE_EVENTS) do |data|
78
- Bidi2pdf.logger.debug "Received event: #{data["method"]}, params: #{data["params"]}"
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(@client)
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
- # rubocop:disable Metrics/AbcSize
89
- def websocket_url
90
- return @websocket_url if @websocket_url
112
+ def create_new_session
113
+ session_data = exec_api_call(session_request)
114
+ Bidi2pdf.logger.debug "Session data: #{session_data}"
91
115
 
92
- if %w[ws wss].include?(session_uri.scheme)
93
- @websocket_url = session_uri.to_s
94
- return @websocket_url
95
- end
116
+ value = session_data["value"]
117
+ handle_error(value) if value.nil? || value["error"]
96
118
 
97
- args = %w[
98
- --disable-gpu
99
- --disable-popup-blocking
100
- --disable-hang-monitor
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
- args << "--headless" if @headless
127
+ def session_request
128
+ session_chrome_args = chrome_args.dup
129
+ session_chrome_args << "--headless" if @headless
104
130
 
105
- session_request = {
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
- response = Net::HTTP.post(session_uri, session_request.to_json, "Content-Type" => "application/json")
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
- Bidi2pdf.logger.debug "Response code: #{response.code}"
125
- Bidi2pdf.logger.debug "Response body: #{response.body}"
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
- session_data = JSON.parse(response.body)
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
- Bidi2pdf.logger.debug "Session data: #{session_data}"
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
- session_id = session_data["value"]["sessionId"]
132
- @websocket_url = session_data["value"]["capabilities"]["webSocketUrl"]
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
- Bidi2pdf.logger.info "Created session with ID: #{session_id}"
135
- Bidi2pdf.logger.info "WebSocket URL: #{@websocket_url}"
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
- @websocket_url
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
- # rubocop:enable Metrics/AbcSize
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
- res = client.send_cmd_and_wait("browser.createUserContext", {}) do |response|
18
- response["result"]["userContext"]
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
- Bidi2pdf.logger.debug "User context created: #{res.inspect}"
20
+ response["result"]["userContext"]
21
+ end
22
22
 
23
- res
24
- end
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
- expiry = Time.now.to_i + ttl
39
- client.send_cmd_and_wait("storage.setCookie", {
40
- cookie: {
41
- name: name,
42
- value: {
43
- type: "string",
44
- value: value
45
- },
46
- domain: domain,
47
- path: path,
48
- secure: secure,
49
- httpOnly: http_only,
50
- sameSite: same_site,
51
- expiry: expiry
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
- client.send_cmd_and_wait("browsingContext.create", {
65
- type: "window",
66
- userContext: context_id
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)
@@ -42,6 +42,8 @@ module Bidi2pdf
42
42
 
43
43
  def remove_error_listener(block) = socket_events.off(:error, block)
44
44
 
45
+ private
46
+
45
47
  def setup_message_handler
46
48
  that = self
47
49
 
@@ -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! :url
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!(*keys)
119
- keys.each do |key|
120
- raise Thor::Error, "Missing required option: --#{key.to_s.tr("_", "-")}" unless merged_options[key]
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
@@ -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
@@ -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
- @tab.open_page(@url)
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"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bidi2pdf
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
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::DEBUG
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