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
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class CreateTab < CreateWindow
7
+ def type = "tab"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class CreateWindow
7
+ include Base
8
+
9
+ def initialize(user_context_id: nil, reference_context: nil, background: false)
10
+ @user_context_id = user_context_id
11
+ @reference_context = reference_context
12
+ @background = background
13
+ end
14
+
15
+ def method_name
16
+ "browsingContext.create"
17
+ end
18
+
19
+ def params
20
+ {
21
+ type: type,
22
+ userContext: @user_context_id,
23
+ referenceContext: @reference_context,
24
+ background: @background
25
+ }.compact
26
+ end
27
+
28
+ def type = "window"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class GetUserContexts
7
+ include Base
8
+
9
+ def method_name
10
+ "browser.getUserContexts"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class NetworkContinue
7
+ include Base
8
+
9
+ attr_reader :request, :headers
10
+
11
+ def initialize(request:, headers:)
12
+ @headers = headers
13
+ @request = request
14
+ end
15
+
16
+ def method_name
17
+ "network.continueRequest"
18
+ end
19
+
20
+ def params
21
+ {
22
+ request: request,
23
+ headers: headers
24
+ }.compact
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ # Validates parameters for the BiDi method `browsingContext.print`.
7
+ #
8
+ # Allowed structure of the params hash:
9
+ #
10
+ # {
11
+ # background: Boolean (optional, default: false) – print background graphics,
12
+ # margin: {
13
+ # top: Float >= 0.0 (optional, default: 1.0),
14
+ # bottom: Float >= 0.0 (optional, default: 1.0),
15
+ # left: Float >= 0.0 (optional, default: 1.0),
16
+ # right: Float >= 0.0 (optional, default: 1.0)
17
+ # },
18
+ # orientation: "portrait" or "landscape" (optional, default: "portrait"),
19
+ # page: {
20
+ # width: Float >= 0.0352 (optional, default: 21.59),
21
+ # height: Float >= 0.0352 (optional, default: 27.94)
22
+ # },
23
+ # pageRanges: Array of Integers or Strings (optional),
24
+ # scale: Float between 0.1 and 2.0 (optional, default: 1.0),
25
+ # shrinkToFit: Boolean (optional, default: true)
26
+ # }
27
+ #
28
+ # This validator checks presence, types, allowed ranges, and values,
29
+ # and raises ArgumentError with a descriptive message if validation fails.
30
+ class PrintParametersValidator
31
+ def self.validate!(params)
32
+ new(params).validate!
33
+ end
34
+
35
+ def initialize(params)
36
+ @params = params
37
+ end
38
+
39
+ def validate!
40
+ raise ArgumentError, "params must be a Hash" unless @params.is_a?(Hash)
41
+
42
+ validate_boolean(:background)
43
+ validate_boolean(:shrinkToFit)
44
+ validate_orientation
45
+ validate_scale
46
+ validate_page_ranges
47
+ validate_margin
48
+ validate_page_size
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ def validate_boolean(key)
56
+ return unless @params.key?(key)
57
+ return if [true, false].include?(@params[key])
58
+
59
+ raise ArgumentError, ":#{key} must be a boolean"
60
+ end
61
+
62
+ def validate_orientation
63
+ return unless @params.key?(:orientation)
64
+ return if %w[portrait landscape].include?(@params[:orientation])
65
+
66
+ raise ArgumentError, ":orientation must be 'portrait' or 'landscape'"
67
+ end
68
+
69
+ def validate_scale
70
+ return unless @params.key?(:scale)
71
+
72
+ scale = @params[:scale]
73
+ return if scale.is_a?(Numeric) && scale >= 0.1 && scale <= 2.0
74
+
75
+ raise ArgumentError, ":scale must be a number between 0.1 and 2.0"
76
+ end
77
+
78
+ def validate_page_ranges
79
+ return unless @params.key?(:pageRanges)
80
+ unless @params[:pageRanges].is_a?(Array) &&
81
+ @params[:pageRanges].all? { |v| v.is_a?(Integer) || v.is_a?(String) }
82
+ raise ArgumentError, ":pageRanges must be an array of integers or strings"
83
+ end
84
+ end
85
+
86
+ def validate_margin
87
+ return unless @params.key?(:margin)
88
+
89
+ margin = @params[:margin]
90
+ raise ArgumentError, ":margin must be a Hash" unless margin.is_a?(Hash)
91
+
92
+ %i[top bottom left right].each do |side|
93
+ next unless margin.key?(side)
94
+
95
+ val = margin[side]
96
+ raise ArgumentError, "margin[:#{side}] must be a float >= 0.0" unless val.is_a?(Numeric) && val >= 0.0
97
+ end
98
+ end
99
+
100
+ def validate_page_size
101
+ return unless @params.key?(:page)
102
+
103
+ page = @params[:page]
104
+ raise ArgumentError, ":page must be a Hash" unless page.is_a?(Hash)
105
+
106
+ %i[width height].each do |dim|
107
+ next unless page.key?(dim)
108
+
109
+ val = page[dim]
110
+ raise ArgumentError, "page[:#{dim}] must be a float >= 0.0352" unless val.is_a?(Numeric) && val >= 0.0352
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class ProvideCredentials
7
+ include Base
8
+
9
+ def initialize(request:, username:, password:)
10
+ @request = request
11
+ @username = username
12
+ @password = password
13
+ end
14
+
15
+ def params
16
+ {
17
+ request: @request,
18
+ action: "provideCredentials",
19
+ credentials: {
20
+ type: "password",
21
+ username: @username,
22
+ password: @password
23
+ }
24
+ }
25
+ end
26
+
27
+ def method_name
28
+ "network.continueWithAuth"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class ScriptEvaluate
7
+ include Base
8
+
9
+ def initialize(expression:,
10
+ context:,
11
+ await_promise: true)
12
+ @expression = expression
13
+ @context = context
14
+ @await_promise = await_promise
15
+ end
16
+
17
+ def params
18
+ {
19
+ expression: @expression,
20
+ target: {
21
+ context: @context
22
+ },
23
+ awaitPromise: @await_promise
24
+ }
25
+ end
26
+
27
+ def method_name
28
+ "script.evaluate"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class SessionEnd
7
+ include Base
8
+
9
+ def method_name
10
+ "session.end"
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 SessionStatus
7
+ include Base
8
+
9
+ def method_name
10
+ "session.status"
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 SessionSubscribe
7
+ include Base
8
+
9
+ attr_reader :events
10
+
11
+ def initialize(events:)
12
+ @events = events
13
+ end
14
+
15
+ def method_name
16
+ "session.subscribe"
17
+ end
18
+
19
+ def params
20
+ { events: events }.compact
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module Bidi
5
+ module Commands
6
+ class SetTabCookie
7
+ include Base
8
+
9
+ attr_reader :name, :value, :domain, :path, :secure, :http_only, :same_site, :ttl, :browsing_context_id
10
+
11
+ def initialize(name:,
12
+ value:,
13
+ domain:,
14
+ browsing_context_id:,
15
+ path: "/",
16
+ secure: true,
17
+ http_only: false,
18
+ same_site: "strict",
19
+ ttl: 30)
20
+ @name = name
21
+ @value = value
22
+ @domain = domain
23
+ @path = path
24
+ @secure = secure
25
+ @http_only = http_only
26
+ @same_site = same_site
27
+ @ttl = ttl
28
+ @browsing_context_id = browsing_context_id
29
+ end
30
+
31
+ def expiry
32
+ Time.now.to_i + ttl
33
+ end
34
+
35
+ def method_name
36
+ "storage.setCookie"
37
+ end
38
+
39
+ def params
40
+ {
41
+ cookie: {
42
+ name: name,
43
+ value: {
44
+ type: "string",
45
+ value: value
46
+ },
47
+ domain: domain,
48
+ path: path,
49
+ secure: secure,
50
+ httpOnly: http_only,
51
+ sameSite: same_site,
52
+ expiry: expiry
53
+ },
54
+ partition: {
55
+ type: "context",
56
+ context: browsing_context_id
57
+ }
58
+ }.compact
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end