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
@@ -3,7 +3,8 @@
3
3
  require "base64"
4
4
 
5
5
  require_relative "network_events"
6
- require_relative "print_parameters_validator"
6
+ require_relative "auth_interceptor"
7
+ require_relative "add_headers_interceptor"
7
8
 
8
9
  module Bidi2pdf
9
10
  module Bidi
@@ -20,10 +21,8 @@ module Bidi2pdf
20
21
  end
21
22
 
22
23
  def create_browser_tab
23
- client.send_cmd_and_wait("browsingContext.create", {
24
- type: "tab",
25
- userContext: @user_context_id
26
- }) do |response|
24
+ cmd = Bidi2pdf::Bidi::Commands::CreateTab.new(user_context_id: user_context_id)
25
+ client.send_cmd_and_wait(cmd) do |response|
27
26
  tab_browsing_context_id = response["result"]["context"]
28
27
 
29
28
  BrowserTab.new(client, tab_browsing_context_id, user_context_id).tap do |tab|
@@ -43,26 +42,18 @@ module Bidi2pdf
43
42
  same_site: "strict",
44
43
  ttl: 30
45
44
  )
46
- expiry = Time.now.to_i + ttl
47
- client.send_cmd_and_wait("storage.setCookie", {
48
- cookie: {
49
- name: name,
50
- value: {
51
- type: "string",
52
- value: value
53
- },
54
- domain: domain,
55
- path: path,
56
- secure: secure,
57
- httpOnly: http_only,
58
- sameSite: same_site,
59
- expiry: expiry
60
- },
61
- partition: {
62
- type: "context",
63
- context: browsing_context_id
64
- }
65
- }) do |response|
45
+ cmd = Bidi2pdf::Bidi::Commands::SetTabCookie.new(
46
+ browsing_context_id: browsing_context_id,
47
+ name: name,
48
+ value: value,
49
+ domain: domain,
50
+ path: path,
51
+ secure: secure,
52
+ http_only: http_only,
53
+ same_site: same_site,
54
+ ttl: ttl
55
+ )
56
+ client.send_cmd_and_wait(cmd) do |response|
66
57
  Bidi2pdf.logger.debug "Cookie set: #{response.inspect}"
67
58
  end
68
59
  end
@@ -71,43 +62,42 @@ module Bidi2pdf
71
62
  headers:,
72
63
  url_patterns:
73
64
  )
74
- client.add_headers_interceptor(
65
+ AddHeadersInterceptor.new(
75
66
  context: browsing_context_id,
76
67
  url_patterns: url_patterns,
77
68
  headers: headers
78
- )
69
+ ).tap { |interceptor| interceptor.register_with_client(client: client) }
79
70
  end
80
71
 
81
72
  def basic_auth(username:, password:, url_patterns:)
82
- client.add_auth_interceptor(
73
+ AuthInterceptor.new(
83
74
  context: browsing_context_id,
84
75
  url_patterns: url_patterns,
85
- username: username,
86
- password: password
87
- )
76
+ username: username, password: password
77
+ ).tap { |interceptor| interceptor.register_with_client(client: client) }
88
78
  end
89
79
 
90
80
  def open_page(url)
91
81
  client.on_event("network.responseStarted", "network.responseCompleted", "network.fetchError",
92
82
  &network_events.method(:handle_event))
93
83
 
94
- client.send_cmd_and_wait("browsingContext.navigate", {
95
- url: url,
96
- context: browsing_context_id,
97
- wait: "complete"
98
- }) do |response|
84
+ cmd = Bidi2pdf::Bidi::Commands::BrowsingContextNavigate.new url: url, context: browsing_context_id
85
+
86
+ client.send_cmd_and_wait(cmd) do |response|
99
87
  Bidi2pdf.logger.debug "Navigated to page url: #{url} response: #{response}"
100
88
  end
101
89
  end
102
90
 
91
+ def view_html_page(html_content)
92
+ base64_encoded = Base64.strict_encode64(html_content)
93
+ data_url = "data:text/html;charset=utf-8;base64,#{base64_encoded}"
94
+
95
+ open_page(data_url)
96
+ end
97
+
103
98
  def execute_script(script)
104
- client.send_cmd_and_wait("script.evaluate", {
105
- expression: script,
106
- target: {
107
- context: browsing_context_id
108
- },
109
- awaitPromise: true
110
- }) do |response|
99
+ cmd = Bidi2pdf::Bidi::Commands::ScriptEvaluate.new context: browsing_context_id, expression: script
100
+ client.send_cmd_and_wait(cmd) do |response|
111
101
  Bidi2pdf.logger.debug "Script Result: #{response.inspect}"
112
102
 
113
103
  response["result"]
@@ -128,37 +118,41 @@ module Bidi2pdf
128
118
  @open = false
129
119
  end
130
120
 
131
- # rubocop:disable Metrics/AbcSize
132
- def print(outputfile, print_options: { background: true })
133
- PrintParametersValidator.validate!(print_options)
134
-
135
- cmd_params = (print_options || {}).merge(context: browsing_context_id)
121
+ # rubocop: disable Metrics/AbcSize, Metrics/PerceivedComplexity
122
+ def print(outputfile = nil, print_options: { background: true }, &block)
123
+ cmd = Bidi2pdf::Bidi::Commands::BrowsingContextPrint.new context: browsing_context_id, print_options: print_options
136
124
 
137
- client.send_cmd_and_wait("browsingContext.print", cmd_params) do |response|
125
+ client.send_cmd_and_wait(cmd) do |response|
138
126
  if response["result"]
139
127
  pdf_base64 = response["result"]["data"]
140
128
 
141
129
  if outputfile
130
+ raise PrintError, "Folder does not exist: #{File.dirname(outputfile)}" unless File.directory?(File.dirname(outputfile))
131
+
142
132
  File.binwrite(outputfile, Base64.decode64(pdf_base64))
143
133
  Bidi2pdf.logger.info "PDF saved as '#{outputfile}'."
144
134
  else
145
135
  Bidi2pdf.logger.info "PDF generated successfully."
146
136
  end
147
137
 
148
- return pdf_base64 unless outputfile
138
+ block.call(pdf_base64) if block_given?
139
+
140
+ return pdf_base64 unless outputfile || block_given?
149
141
  else
150
142
  Bidi2pdf.logger.error "Error printing: #{response}"
151
143
  end
152
144
  end
153
145
  end
154
146
 
155
- # rubocop:enable Metrics/AbcSize
147
+ # rubocop: enable Metrics/AbcSize, Metrics/PerceivedComplexity
156
148
 
157
149
  private
158
150
 
159
151
  def close_context
160
- client.send_cmd_and_wait("browsingContext.close", { context: browsing_context_id }) do |response|
161
- Bidi2pdf.logger.debug "Browsing context closed: #{response}"
152
+ that = self
153
+ cmd = Bidi2pdf::Bidi::Commands::BrowsingContextClose.new context: browsing_context_id
154
+ client.send_cmd_and_wait(cmd) do |response|
155
+ Bidi2pdf.logger.info "Browsing context closed: #{that.browsing_context_id} #{response}"
162
156
  end
163
157
  end
164
158
 
@@ -4,10 +4,9 @@ require "json"
4
4
  require "websocket-client-simple"
5
5
 
6
6
  require_relative "web_socket_dispatcher"
7
- require_relative "add_headers_interceptor"
8
- require_relative "auth_interceptor"
9
7
  require_relative "command_manager"
10
8
  require_relative "connection_manager"
9
+ require_relative "commands"
11
10
 
12
11
  module Bidi2pdf
13
12
  module Bidi
@@ -19,20 +18,21 @@ module Bidi2pdf
19
18
  def initialize(ws_url)
20
19
  @ws_url = ws_url
21
20
  @started = false
21
+ @connection_manager = ConnectionManager.new(logger: Bidi2pdf.logger)
22
22
  end
23
23
 
24
24
  def start
25
25
  return @socket if started?
26
26
 
27
- @socket = WebSocket::Client::Simple.connect(ws_url)
28
-
29
- @connection_manager = ConnectionManager.new(logger: Bidi2pdf.logger)
30
- @command_manager = CommandManager.new(@socket, logger: Bidi2pdf.logger)
27
+ WebSocket::Client::Simple.connect(ws_url) do |socket|
28
+ @socket = socket
29
+ @command_manager = CommandManager.new(@socket, logger: Bidi2pdf.logger)
31
30
 
32
- dispatcher.on_open { @connection_manager.mark_connected }
33
- dispatcher.on_message { |data| handle_response_to_cmd(data) }
31
+ dispatcher.on_open { @connection_manager.mark_connected }
32
+ dispatcher.on_message { |data| handle_response_to_cmd(data) }
33
+ dispatcher.start_listening
34
+ end
34
35
 
35
- dispatcher.start_listening
36
36
  @started = true
37
37
 
38
38
  @socket
@@ -42,15 +42,23 @@ module Bidi2pdf
42
42
 
43
43
  def wait_until_open(timeout: Bidi2pdf.default_timeout)
44
44
  @connection_manager.wait_until_open(timeout: timeout)
45
+ rescue Bidi2pdf::WebsocketError => e
46
+ raise Bidi2pdf::WebsocketError, "Client#start must be called within #{timeout} sec." unless started?
47
+
48
+ raise e
45
49
  end
46
50
 
47
- def send_cmd(method, params = {})
48
- @command_manager.send_cmd(method, params)
51
+ def send_cmd(cmd)
52
+ raise Bidi2pdf::ClientError, "Client#start must be called before" unless started?
53
+
54
+ @command_manager.send_cmd(cmd)
49
55
  end
50
56
 
51
- def send_cmd_and_wait(method, params = {}, timeout: Bidi2pdf.default_timeout, &block)
52
- timed("Command #{method}") do
53
- @command_manager.send_cmd_and_wait(method, params, timeout: timeout, &block)
57
+ def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout, &block)
58
+ raise Bidi2pdf::ClientError, "Client#start must be called before" unless started?
59
+
60
+ timed("Command #{cmd.inspect}") do
61
+ @command_manager.send_cmd_and_wait(cmd, timeout: timeout, &block)
54
62
  end
55
63
  end
56
64
 
@@ -64,7 +72,8 @@ module Bidi2pdf
64
72
 
65
73
  def on_event(*names, &block)
66
74
  names.each { |name| dispatcher.on_event(name, &block) }
67
- send_cmd("session.subscribe", { events: names }) if names.any?
75
+ cmd = Bidi2pdf::Bidi::Commands::SessionSubscribe.new(events: names)
76
+ send_cmd(cmd) if names.any?
68
77
  end
69
78
 
70
79
  def remove_message_listener(block) = dispatcher.remove_message_listener(block)
@@ -73,28 +82,6 @@ module Bidi2pdf
73
82
  names.each { |event_name| dispatcher.remove_event_listener(event_name, block) }
74
83
  end
75
84
 
76
- def add_headers_interceptor(context:, url_patterns:, headers:)
77
- add_interceptor(
78
- context: context,
79
- url_patterns: url_patterns,
80
- phase: "beforeRequestSent",
81
- event: "network.beforeRequestSent",
82
- interceptor_class: AddHeadersInterceptor,
83
- extra_args: { headers: headers }
84
- )
85
- end
86
-
87
- def add_auth_interceptor(context:, url_patterns:, username:, password:)
88
- add_interceptor(
89
- context: context,
90
- url_patterns: url_patterns,
91
- phase: "authRequired",
92
- event: "network.authRequired",
93
- interceptor_class: AuthInterceptor,
94
- extra_args: { username: username, password: password }
95
- )
96
- end
97
-
98
85
  def close
99
86
  return unless @socket
100
87
 
@@ -120,21 +107,6 @@ module Bidi2pdf
120
107
  Bidi2pdf.logger.warn "Unknown response: #{data.inspect}"
121
108
  end
122
109
  end
123
-
124
- def add_interceptor(context:, url_patterns:, phase:, event:, interceptor_class:, extra_args: {})
125
- send_cmd_and_wait("network.addIntercept", {
126
- context: context,
127
- phases: [phase],
128
- urlPatterns: url_patterns
129
- }) do |response|
130
- id = response["result"]["intercept"]
131
- Bidi2pdf.logger.debug "Interceptor added: #{id}"
132
-
133
- interceptor_class.new(id, **extra_args, client: self).tap do |interceptor|
134
- on_event(event, &interceptor.method(:handle_event))
135
- end
136
- end
137
- end
138
110
  end
139
111
  end
140
112
  end
@@ -3,18 +3,35 @@
3
3
  module Bidi2pdf
4
4
  module Bidi
5
5
  class CommandManager
6
+ class << self
7
+ def initialize_counter
8
+ @id = 0
9
+ @id_mutex = Mutex.new
10
+ end
11
+
12
+ def next_id = @id_mutex.synchronize { @id += 1 }
13
+ end
14
+
15
+ initialize_counter
16
+
6
17
  def initialize(socket, logger:)
7
18
  @socket = socket
8
19
  @logger = logger
9
20
 
10
- @id = 0
11
- @next_id_mutex = Mutex.new
12
21
  @pending_responses = {}
22
+ @initiated_cmds = {}
13
23
  end
14
24
 
15
- def send_cmd(method, params = {})
25
+ def send_cmd(cmd, store_response: false)
16
26
  id = next_id
17
- payload = { id: id, method: method, params: params }
27
+
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)
18
35
 
19
36
  @logger.debug "Sending command: #{redact_sensitive_fields(payload).inspect}"
20
37
  @socket.send(payload.to_json)
@@ -22,40 +39,46 @@ module Bidi2pdf
22
39
  id
23
40
  end
24
41
 
25
- def send_cmd_and_wait(method, params = {}, timeout: Bidi2pdf.default_timeout)
26
- id = send_cmd(method, params)
27
- queue = @pending_responses[id]
42
+ def send_cmd_and_wait(cmd, timeout: Bidi2pdf.default_timeout)
43
+ id = send_cmd(cmd, store_response: true)
44
+ response = pop_response id, timeout: timeout
28
45
 
29
- response = queue.pop(timeout: timeout)
30
- raise_timeout_error(id, method, params) if response.nil?
31
- raise "Error response: #{response["error"]}" if response["error"]
46
+ raise_timeout_error(id, cmd) if response.nil?
47
+ raise CmdError, "Error response: #{response["error"]} #{cmd.inspect}" if response["error"]
32
48
 
33
49
  block_given? ? yield(response) : response
34
50
  ensure
35
51
  @pending_responses.delete(id)
36
52
  end
37
53
 
38
- def queue_for(id)
39
- @pending_responses[id]
54
+ def pop_response(id, timeout:)
55
+ raise CmdResponseNotStoredError, "No response stored for command ID #{id} or already popped or this command was not send" unless @pending_responses.key?(id)
56
+
57
+ @pending_responses[id].pop(timeout: timeout)
58
+ ensure
59
+ @pending_responses.delete(id)
40
60
  end
41
61
 
42
62
  def handle_response(data)
43
- if (id = data["id"]) && @pending_responses.key?(id)
44
- @pending_responses[id]&.push(data)
45
- else
46
- false
63
+ if (id = data["id"])
64
+ if @pending_responses.key?(id)
65
+ @pending_responses[id]&.push(data)
66
+ return true
67
+ elsif @initiated_cmds.key?(id)
68
+ @logger.error "Received error: #{data["error"]} for cmd: #{id}" if data["error"]
69
+
70
+ return @initiated_cmds.delete(id)
71
+ end
47
72
  end
73
+
74
+ false
48
75
  end
49
76
 
50
77
  private
51
78
 
52
- def next_id
53
- @next_id_mutex.synchronize do
54
- @id += 1
55
- @pending_responses[@id] = Thread::Queue.new
56
- @id
57
- end
58
- end
79
+ def init_queue_for(id) = @pending_responses[id] = Thread::Queue.new
80
+
81
+ def next_id = self.class.next_id
59
82
 
60
83
  def redact_sensitive_fields(obj, sensitive_keys = %w[value token password authorization username])
61
84
  case obj
@@ -71,11 +94,10 @@ module Bidi2pdf
71
94
  end
72
95
  end
73
96
 
74
- def raise_timeout_error(id, method, params)
75
- # rubocop:disable Layout/LineLength
76
- @logger.error "Timeout waiting for response to command #{id}, cmd: #{method}, params: #{redact_sensitive_fields(params).inspect}"
77
- # rubocop:enable Layout/LineLength
78
- raise "Timeout waiting for response to command ID #{id}"
97
+ def raise_timeout_error(id, cmd)
98
+ @logger.error "Timeout waiting for response to command #{id}, cmd: #{cmd.inspect}"
99
+
100
+ raise CmdTimeoutError, "Timeout waiting for response to command ID #{id}"
79
101
  end
80
102
  end
81
103
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class AddIntercept
7
+ include Base
8
+
9
+ BEFORE_REQUEST = "beforeRequestSent"
10
+ RESPONSE_STARTED = "responseStarted"
11
+ AUTH_REQUIRED = "authRequired"
12
+
13
+ def initialize(context:, phases:, url_patterns:)
14
+ @context = context
15
+ @phases = phases
16
+ @url_patterns = url_patterns
17
+
18
+ validate_phases!
19
+ end
20
+
21
+ def method_name
22
+ "network.addIntercept"
23
+ end
24
+
25
+ def params
26
+ {
27
+ context: @context,
28
+ phases: @phases,
29
+ urlPatterns: @url_patterns
30
+ }.compact
31
+ end
32
+
33
+ def validate_phases!
34
+ valid_phases = [BEFORE_REQUEST, RESPONSE_STARTED, AUTH_REQUIRED]
35
+
36
+ raise ArgumentError, "Unsupported phase(s): #{@phases}" unless @phases.all? { |phase| valid_phases.include?(phase) }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ module Base
7
+ def method_name = raise(NotImplementedError, "method_name must be implemented in subclass")
8
+
9
+ def params = {}
10
+
11
+ def as_payload(id)
12
+ {
13
+ id: id,
14
+ method: method_name,
15
+ params: params
16
+ }
17
+ end
18
+
19
+ # rubocop: disable Metrics/AbcSize
20
+ def ==(other)
21
+ return false unless other.respond_to?(:method_name) && other.respond_to?(:params)
22
+
23
+ return false unless method_name == other.method_name
24
+
25
+ return false unless params.keys.sort == other.params.keys.sort
26
+
27
+ params.all? { |key, value| other.params.key?(key) && value == other.params[key] }
28
+ end
29
+
30
+ # rubocop: enable Metrics/AbcSize
31
+
32
+ # Hash equality comparison
33
+ def eql?(other)
34
+ return false unless other.is_a?(Bidi2pdf::Bidi::Commands::Base)
35
+
36
+ self == other
37
+ end
38
+
39
+ def hash
40
+ [method_name, params].hash
41
+ end
42
+
43
+ def inspect
44
+ attributes = redact_sensitive_fields({ method_name: method_name, params: params })
45
+
46
+ "#<#{self.class}:#{object_id} #{attributes}>"
47
+ end
48
+
49
+ private
50
+
51
+ def redact_sensitive_fields(obj, sensitive_keys = %w[value token password authorization username])
52
+ case obj
53
+ when Hash
54
+ obj.transform_values.with_index do |v, idx|
55
+ k = obj.keys[idx]
56
+ sensitive_keys.include?(k.to_s.downcase) ? "[REDACTED]" : redact_sensitive_fields(v, sensitive_keys)
57
+ end
58
+ when Array
59
+ obj.map { |item| redact_sensitive_fields(item, sensitive_keys) }
60
+ else
61
+ obj
62
+ end
63
+ end
64
+
65
+ def raise_timeout_error(id, method, params)
66
+ @logger.error "Timeout waiting for response to command #{id}, cmd: #{method}, params: #{redact_sensitive_fields(params).inspect}"
67
+
68
+ raise CmdTimeoutError, "Timeout waiting for response to command ID #{id}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class BrowserClose
7
+ include Base
8
+
9
+ def method_name
10
+ "browser.close"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class BrowserCreateUserContext
7
+ include Base
8
+
9
+ def method_name
10
+ "browser.createUserContext"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class BrowsingContextClose
7
+ include Base
8
+
9
+ def initialize(context:)
10
+ @context = context
11
+ end
12
+
13
+ def params
14
+ {
15
+ context: @context
16
+ }
17
+ end
18
+
19
+ def method_name
20
+ "browsingContext.close"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class BrowsingContextNavigate
7
+ include Base
8
+
9
+ def initialize(url:,
10
+ context:,
11
+ wait: "complete")
12
+ @url = url
13
+ @context = context
14
+ @wait = wait
15
+ end
16
+
17
+ def params
18
+ {
19
+ url: @url,
20
+ context: @context,
21
+ wait: @wait
22
+ }
23
+ end
24
+
25
+ def method_name
26
+ "browsingContext.navigate"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "print_parameters_validator"
4
+
5
+ module Bidi2pdf
6
+ module Bidi
7
+ module Commands
8
+ class BrowsingContextPrint
9
+ include Base
10
+
11
+ def initialize(context:, print_options:)
12
+ @context = context
13
+ @print_options = print_options || { background: true }
14
+
15
+ PrintParametersValidator.validate!(@print_options)
16
+ end
17
+
18
+ def params
19
+ @print_options.merge(context: @context)
20
+ end
21
+
22
+ def method_name
23
+ "browsingContext.print"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class CancelAuth
7
+ include Base
8
+
9
+ def initialize(request:)
10
+ @request = request
11
+ end
12
+
13
+ def params
14
+ {
15
+ request: @request,
16
+ action: "cancel"
17
+ }
18
+ end
19
+
20
+ def method_name
21
+ "network.continueWithAuth"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end