bidi2pdf 0.1.3 → 0.1.4

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -1
  3. data/CHANGELOG.md +27 -3
  4. data/docker/Dockerfile.chromedriver +5 -0
  5. data/docker/entrypoint.sh +11 -1
  6. data/lib/bidi2pdf/bidi/add_headers_interceptor.rb +18 -21
  7. data/lib/bidi2pdf/bidi/auth_interceptor.rb +31 -38
  8. data/lib/bidi2pdf/bidi/browser_tab.rb +32 -52
  9. data/lib/bidi2pdf/bidi/client.rb +24 -52
  10. data/lib/bidi2pdf/bidi/command_manager.rb +50 -28
  11. data/lib/bidi2pdf/bidi/commands/add_intercept.rb +41 -0
  12. data/lib/bidi2pdf/bidi/commands/base.rb +73 -0
  13. data/lib/bidi2pdf/bidi/commands/browser_close.rb +15 -0
  14. data/lib/bidi2pdf/bidi/commands/browser_create_user_context.rb +15 -0
  15. data/lib/bidi2pdf/bidi/commands/browsing_context_close.rb +25 -0
  16. data/lib/bidi2pdf/bidi/commands/browsing_context_navigate.rb +31 -0
  17. data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +28 -0
  18. data/lib/bidi2pdf/bidi/commands/cancel_auth.rb +26 -0
  19. data/lib/bidi2pdf/bidi/commands/create_tab.rb +11 -0
  20. data/lib/bidi2pdf/bidi/commands/create_window.rb +32 -0
  21. data/lib/bidi2pdf/bidi/commands/get_user_contexts.rb +15 -0
  22. data/lib/bidi2pdf/bidi/commands/network_continue.rb +29 -0
  23. data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +116 -0
  24. data/lib/bidi2pdf/bidi/commands/provide_credentials.rb +33 -0
  25. data/lib/bidi2pdf/bidi/commands/script_evaluate.rb +33 -0
  26. data/lib/bidi2pdf/bidi/commands/session_end.rb +15 -0
  27. data/lib/bidi2pdf/bidi/commands/session_status.rb +15 -0
  28. data/lib/bidi2pdf/bidi/commands/session_subscribe.rb +25 -0
  29. data/lib/bidi2pdf/bidi/commands/set_tab_cookie.rb +63 -0
  30. data/lib/bidi2pdf/bidi/commands/set_usercontext_cookie.rb +67 -0
  31. data/lib/bidi2pdf/bidi/commands.rb +27 -0
  32. data/lib/bidi2pdf/bidi/connection_manager.rb +16 -13
  33. data/lib/bidi2pdf/bidi/event_manager.rb +2 -0
  34. data/lib/bidi2pdf/bidi/interceptor.rb +75 -0
  35. data/lib/bidi2pdf/bidi/network_events.rb +0 -1
  36. data/lib/bidi2pdf/bidi/session.rb +139 -65
  37. data/lib/bidi2pdf/bidi/user_context.rb +25 -31
  38. data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +2 -0
  39. data/lib/bidi2pdf/chromedriver_manager.rb +2 -1
  40. data/lib/bidi2pdf/cli.rb +1 -3
  41. data/lib/bidi2pdf/launcher.rb +3 -1
  42. data/lib/bidi2pdf/version.rb +1 -1
  43. data/lib/bidi2pdf.rb +12 -0
  44. data/sig/bidi2pdf/bidi/command_manager.rbs +41 -0
  45. data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +21 -0
  46. data/sig/bidi2pdf/bidi/commands/base.rbs +27 -0
  47. data/sig/bidi2pdf/bidi/commands/browser_close.rbs +9 -0
  48. data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +9 -0
  49. data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +11 -0
  50. data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +15 -0
  51. data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +14 -0
  52. data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +11 -0
  53. data/sig/bidi2pdf/bidi/commands/create_tab.rbs +9 -0
  54. data/sig/bidi2pdf/bidi/commands/create_window.rbs +19 -0
  55. data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +9 -0
  56. data/sig/bidi2pdf/bidi/commands/network_continue.rbs +19 -0
  57. data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +53 -0
  58. data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +15 -0
  59. data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +17 -0
  60. data/sig/bidi2pdf/bidi/commands/session_end.rbs +9 -0
  61. data/sig/bidi2pdf/bidi/commands/session_status.rbs +9 -0
  62. data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +15 -0
  63. data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +31 -0
  64. data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +27 -0
  65. data/sig/bidi2pdf/bidi/commands.rbs +6 -0
  66. data/sig/bidi2pdf/bidi/connection_manager.rbs +17 -0
  67. data/sig/bidi2pdf/bidi/interceptor.rbs +31 -0
  68. data/tasks/coverage.rake +16 -0
  69. metadata +65 -11
  70. data/lib/bidi2pdf/bidi/print_parameters_validator.rb +0 -114
  71. data/sig/bidi2pdf/bidi/print_parameters_validator.rbs +0 -44
  72. data/sig/bidi2pdf/chrome/chromedriver_downloader.rbs +0 -11
  73. data/sig/bidi2pdf/chrome/downloader_helper.rbs +0 -9
  74. data/sig/bidi2pdf/chrome/finder.rbs +0 -27
  75. data/sig/bidi2pdf/chrome/platform.rbs +0 -13
  76. data/sig/bidi2pdf/chrome/version_resolver.rbs +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 715f3587349e1680386f0c485f45b91a6096143c07cc8b531ca7e284a9516615
4
- data.tar.gz: 8914fd60cdb04510070dee6b6d7134a94666584583681e26e830824bbf457059
3
+ metadata.gz: f132ccc5d9db5bb621f7e1a0adfa4150b77b4618542ba27873a602cf9c342a40
4
+ data.tar.gz: f3df228dc1de555d14baad4347a1b5b8d0bc8b63f88d6adfbcec86f0da571703
5
5
  SHA512:
6
- metadata.gz: a8e17273531b0131ef9e8b5454154996dbd340d18cbf16a151478b91234e1e53005e0ffb057e16d540f6870b8915bafbdf03831afc2d9b4e9d861494ca13c38f
7
- data.tar.gz: d0a4d3b193fc51b681a2e3b54da5cc7cccf05cf578b908144eb7ca5988327f0ee938b07dce88ca826d37889a469e3f32a7da61c68bbb05b5913731aeb0cc9e38
6
+ metadata.gz: d74e6e1b505493d47f66c2f1cb4d13bc07aacfa82dbba536439e3b458395bbcd7213103e5fbed167a65945af28bbfb239f6510e93bed2838fa583b95352df8d6
7
+ data.tar.gz: '096bb337a61db6d5392ba64c38bf4632695ca647b2fe95a227f3d56b189241cae1b19cc65e98115dff73997bb3df1cdcc2821058611683aa449acd11db4fe66f'
data/.rubocop.yml CHANGED
@@ -20,11 +20,29 @@ RSpec/ExampleLength:
20
20
  Layout/MultilineMethodCallIndentation:
21
21
  Enabled: false
22
22
 
23
+ Layout/FirstArgumentIndentation:
24
+ Enabled: false
25
+
26
+ Layout/ClosingParenthesisIndentation:
27
+ Enabled: false
28
+
23
29
  Layout/FirstHashElementIndentation:
24
30
  EnforcedStyle: consistent
25
31
 
26
32
  Layout/FirstArrayElementIndentation:
27
- EnforcedStyle: consistent
33
+ Enabled: false
34
+
35
+ Layout/MultilineOperationIndentation:
36
+ Enabled: false
37
+
38
+ Layout/BeginEndAlignment:
39
+ Enabled: false
40
+
41
+ Layout/ArrayAlignment:
42
+ Enabled: false
43
+
44
+ Layout/LineLength:
45
+ Enabled: false
28
46
 
29
47
  RSpec/MultipleMemoizedHelpers:
30
48
  Max: 10
data/CHANGELOG.md CHANGED
@@ -6,12 +6,36 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
+ [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.4..HEAD
9
10
 
10
- ## [Unreleased]
11
+ <!-- generated by git-cliff end -->
11
12
 
12
- [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.2..HEAD
13
+ ## [0.1.4] - 2025-04-10
13
14
 
14
- <!-- generated by git-cliff end -->
15
+ ### 🎨 Refactored
16
+
17
+ - Modularize browser commands and remove redundancy by @dieter-medium
18
+ - Implement interceptor validation and unit tests by @dieter-medium
19
+ - Connection handling in ConnectionManager by @dieter-medium
20
+ - Interceptors to enforce common interface methods by @dieter-medium
21
+ - Interceptors to simplify client responsibilities by @dieter-medium
22
+ - Enhance error handling and modularize interceptors by @dieter-medium
23
+
24
+ ### 🐛 Fixed
25
+
26
+ - Resolve race condition between event subscription and emission by @dieter-medium
27
+ - Close custom session on stop function by @dieter-medium
28
+ - Add custom error classes for session management and improve error handling and testability by @dieter-medium
29
+ - Add Chromedriver container support and helper methods for RSpec integration by @dieter-medium
30
+
31
+ ### 🔄 Changed
32
+
33
+ - Enhance command manager with response handling by @dieter-medium
34
+
35
+ ### 🚀 Added
36
+
37
+ - Add randomization to user data directory path by @dieter-medium
38
+ - Introduce command abstraction for improved modularity by @dieter-medium
15
39
 
16
40
  ## [0.1.3] - 2025-04-06
17
41
 
@@ -1,5 +1,7 @@
1
1
  FROM ruby:3.3
2
2
 
3
+ ARG CHROMEDRIVER_PORT=3000
4
+
3
5
  # Install dependencies
4
6
  RUN apt-get update && \
5
7
  apt-get install -y \
@@ -30,4 +32,7 @@ USER appuser
30
32
 
31
33
  RUN gem install chromedriver-binary && ruby -e 'require "chromedriver/binary"; puts Chromedriver::Binary::ChromedriverDownloader.update'
32
34
 
35
+ ENV CHROMEDRIVER_PORT=${CHROMEDRIVER_PORT}
36
+ EXPOSE ${CHROMEDRIVER_PORT}
37
+
33
38
  CMD ["/usr/local/bin/entrypoint.sh"]
data/docker/entrypoint.sh CHANGED
@@ -1,3 +1,13 @@
1
1
  #!/bin/bash
2
2
 
3
- /home/appuser/.webdrivers/chromedriver --port=3000 --headless --whitelisted-ips="" --allowed-origins="*" --disable-dev-shm-usage --verbose
3
+ USER_DATA_DIR=/home/appuser/.cache
4
+ mkdir -p ${USER_DATA_DIR}
5
+
6
+ /home/appuser/.webdrivers/chromedriver --port=${CHROMEDRIVER_PORT} \
7
+ --headless \
8
+ --whitelisted-ips="" \
9
+ --allowed-origins="*" \
10
+ --disable-dev-shm-usage \
11
+ --disable-gpu \
12
+ --user-data-dir=${USER_DATA_DIR} \
13
+ --verbose
@@ -1,13 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "interceptor"
4
+
3
5
  module Bidi2pdf
4
6
  module Bidi
5
7
  class AddHeadersInterceptor
6
- attr_reader :id, :headers
8
+ include Interceptor
9
+
10
+ class << self
11
+ def phases = [Bidi2pdf::Bidi::Commands::AddIntercept::BEFORE_REQUEST]
12
+
13
+ def events = ["network.beforeRequestSent"]
14
+ end
15
+
16
+ attr_reader :headers, :url_patterns, :context
7
17
 
8
- def initialize(id, headers:, client:)
9
- @id = id
10
- @client = client
18
+ def initialize(headers:, url_patterns:, context:)
11
19
  @headers = headers.map do |header|
12
20
  {
13
21
  name: header[:name],
@@ -17,26 +25,15 @@ module Bidi2pdf
17
25
  }
18
26
  }
19
27
  end
20
- end
21
28
 
22
- def handle_event(response)
23
- event_response = response["params"]
24
-
25
- return unless event_response["intercepts"]&.include?(id) && event_response["isBlocked"]
26
-
27
- network_id = event_response["request"]["request"]
28
-
29
- Bidi2pdf.logger.debug "Interceptor #{id} handle event: #{network_id}"
30
-
31
- client.send_cmd "network.continueRequest", {
32
- request: network_id,
33
- headers: headers
34
- }
29
+ @url_patterns = url_patterns
30
+ @context = context
35
31
  end
36
32
 
37
- private
38
-
39
- attr_reader :client
33
+ def process_interception(_event_response, _navigation_id, network_id, _url)
34
+ cmd = Bidi2pdf::Bidi::Commands::NetworkContinue.new request: network_id, headers: headers
35
+ client.send_cmd cmd
36
+ end
40
37
  end
41
38
  end
42
39
  end
@@ -1,67 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "interceptor"
4
+
3
5
  module Bidi2pdf
4
6
  module Bidi
5
7
  class AuthInterceptor
6
- attr_reader :id, :username, :password, :network_ids
8
+ include Interceptor
9
+
10
+ class << self
11
+ def phases = [Bidi2pdf::Bidi::Commands::AddIntercept::AUTH_REQUIRED]
12
+
13
+ def events = ["network.authRequired"]
14
+ end
7
15
 
8
- def initialize(id, username:, password:, client:)
9
- @id = id
10
- @client = client
16
+ attr_reader :headers, :url_patterns, :context, :username, :password, :network_ids
17
+
18
+ def initialize(username:, password:, url_patterns:, context:)
19
+ @url_patterns = url_patterns
20
+ @context = context
11
21
  @username = username
12
22
  @password = password
13
23
  @network_ids = []
14
24
  end
15
25
 
16
- # rubocop:disable Metrics/AbcSize
17
- def handle_event(response)
18
- event_response = response["params"]
19
-
20
- return unless event_response["intercepts"]&.include?(id) && event_response["isBlocked"]
21
-
22
- navigation_id = event_response["navigation"]
23
- network_id = event_response["request"]["request"]
24
- url = event_response["request"]["url"]
25
-
26
- handle_bad_credentials(navigation_id, network_id, url)
26
+ def process_interception(_event_response, navigation_id, network_id, url)
27
+ return if handled_bad_credentials(navigation_id, network_id, url)
27
28
 
28
29
  network_ids << network_id
29
30
 
30
- Bidi2pdf.logger.debug "Auth-Interceptor #{id} handle event: #{navigation_id}/#{network_id}/#{url}"
31
-
32
- client.send_cmd("network.continueWithAuth", {
33
- request: network_id,
34
- action: "provideCredentials",
35
- credentials: {
36
- type: "password",
37
- username: username,
38
- password: password
39
- }
40
- })
41
- end
31
+ cmd = Bidi2pdf::Bidi::Commands::ProvideCredentials.new request: network_id, username: username, password: password
42
32
 
43
- # rubocop:enable Metrics/AbcSize
33
+ client.send_cmd(cmd)
34
+ rescue StandardError => e
35
+ Bidi2pdf.logger.error "Error handling auth event: #{e.message}"
36
+ Bidi2pdf.logger.error e.backtrace.join("\n")
37
+ raise e
38
+ end
44
39
 
45
40
  private
46
41
 
47
- def handle_bad_credentials(navigation_id, network_id, url)
48
- return unless network_ids.include?(network_id)
42
+ def handled_bad_credentials(navigation_id, network_id, url)
43
+ return false unless network_ids.include?(network_id)
49
44
 
50
45
  network_ids.delete(network_id)
51
46
 
52
- Bidi2pdf.logger.debug "Auth-Interceptor #{id} already handled event: #{navigation_id}/#{network_id}/#{url}"
47
+ Bidi2pdf.logger.debug "Auth-Interceptor #{interceptor_id} already handled event: #{navigation_id}/#{network_id}/#{url}"
53
48
 
54
- # rubocop: disable Layout/LineLength
55
49
  Bidi2pdf.logger.error "It seems that the same request is being intercepted multiple times. Check your credentials or the URL you are trying to access. If you are using a proxy, make sure it is configured correctly."
56
50
  # rubocop: enable Layout/LineLength
57
51
 
58
- client.send_cmd("network.continueWithAuth", {
59
- request: network_id,
60
- action: "cancel"
61
- })
62
- end
52
+ cmd = Bidi2pdf::Bidi::Commands::CancelAuth.new request: network_id
53
+
54
+ client.send_cmd(cmd)
63
55
 
64
- attr_reader :client
56
+ true
57
+ end
65
58
  end
66
59
  end
67
60
  end
@@ -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,35 @@ 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
 
103
91
  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|
92
+ cmd = Bidi2pdf::Bidi::Commands::ScriptEvaluate.new context: browsing_context_id, expression: script
93
+ client.send_cmd_and_wait(cmd) do |response|
111
94
  Bidi2pdf.logger.debug "Script Result: #{response.inspect}"
112
95
 
113
96
  response["result"]
@@ -128,13 +111,10 @@ module Bidi2pdf
128
111
  @open = false
129
112
  end
130
113
 
131
- # rubocop:disable Metrics/AbcSize
132
114
  def print(outputfile, print_options: { background: true })
133
- PrintParametersValidator.validate!(print_options)
134
-
135
- cmd_params = (print_options || {}).merge(context: browsing_context_id)
115
+ cmd = Bidi2pdf::Bidi::Commands::BrowsingContextPrint.new context: browsing_context_id, print_options: print_options
136
116
 
137
- client.send_cmd_and_wait("browsingContext.print", cmd_params) do |response|
117
+ client.send_cmd_and_wait(cmd) do |response|
138
118
  if response["result"]
139
119
  pdf_base64 = response["result"]["data"]
140
120
 
@@ -152,13 +132,13 @@ module Bidi2pdf
152
132
  end
153
133
  end
154
134
 
155
- # rubocop:enable Metrics/AbcSize
156
-
157
135
  private
158
136
 
159
137
  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}"
138
+ that = self
139
+ cmd = Bidi2pdf::Bidi::Commands::BrowsingContextClose.new context: browsing_context_id
140
+ client.send_cmd_and_wait(cmd) do |response|
141
+ Bidi2pdf.logger.info "Browsing context closed: #{that.browsing_context_id} #{response}"
162
142
  end
163
143
  end
164
144
 
@@ -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