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.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -1
- data/CHANGELOG.md +27 -3
- data/docker/Dockerfile.chromedriver +5 -0
- data/docker/entrypoint.sh +11 -1
- data/lib/bidi2pdf/bidi/add_headers_interceptor.rb +18 -21
- data/lib/bidi2pdf/bidi/auth_interceptor.rb +31 -38
- data/lib/bidi2pdf/bidi/browser_tab.rb +32 -52
- data/lib/bidi2pdf/bidi/client.rb +24 -52
- data/lib/bidi2pdf/bidi/command_manager.rb +50 -28
- data/lib/bidi2pdf/bidi/commands/add_intercept.rb +41 -0
- data/lib/bidi2pdf/bidi/commands/base.rb +73 -0
- data/lib/bidi2pdf/bidi/commands/browser_close.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/browser_create_user_context.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_close.rb +25 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_navigate.rb +31 -0
- data/lib/bidi2pdf/bidi/commands/browsing_context_print.rb +28 -0
- data/lib/bidi2pdf/bidi/commands/cancel_auth.rb +26 -0
- data/lib/bidi2pdf/bidi/commands/create_tab.rb +11 -0
- data/lib/bidi2pdf/bidi/commands/create_window.rb +32 -0
- data/lib/bidi2pdf/bidi/commands/get_user_contexts.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/network_continue.rb +29 -0
- data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +116 -0
- data/lib/bidi2pdf/bidi/commands/provide_credentials.rb +33 -0
- data/lib/bidi2pdf/bidi/commands/script_evaluate.rb +33 -0
- data/lib/bidi2pdf/bidi/commands/session_end.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/session_status.rb +15 -0
- data/lib/bidi2pdf/bidi/commands/session_subscribe.rb +25 -0
- data/lib/bidi2pdf/bidi/commands/set_tab_cookie.rb +63 -0
- data/lib/bidi2pdf/bidi/commands/set_usercontext_cookie.rb +67 -0
- data/lib/bidi2pdf/bidi/commands.rb +27 -0
- data/lib/bidi2pdf/bidi/connection_manager.rb +16 -13
- data/lib/bidi2pdf/bidi/event_manager.rb +2 -0
- data/lib/bidi2pdf/bidi/interceptor.rb +75 -0
- data/lib/bidi2pdf/bidi/network_events.rb +0 -1
- data/lib/bidi2pdf/bidi/session.rb +139 -65
- data/lib/bidi2pdf/bidi/user_context.rb +25 -31
- data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +2 -0
- data/lib/bidi2pdf/chromedriver_manager.rb +2 -1
- data/lib/bidi2pdf/cli.rb +1 -3
- data/lib/bidi2pdf/launcher.rb +3 -1
- data/lib/bidi2pdf/version.rb +1 -1
- data/lib/bidi2pdf.rb +12 -0
- data/sig/bidi2pdf/bidi/command_manager.rbs +41 -0
- data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +21 -0
- data/sig/bidi2pdf/bidi/commands/base.rbs +27 -0
- data/sig/bidi2pdf/bidi/commands/browser_close.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +11 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +14 -0
- data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +11 -0
- data/sig/bidi2pdf/bidi/commands/create_tab.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/create_window.rbs +19 -0
- data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/network_continue.rbs +19 -0
- data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +53 -0
- data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +17 -0
- data/sig/bidi2pdf/bidi/commands/session_end.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/session_status.rbs +9 -0
- data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +15 -0
- data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +31 -0
- data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +27 -0
- data/sig/bidi2pdf/bidi/commands.rbs +6 -0
- data/sig/bidi2pdf/bidi/connection_manager.rbs +17 -0
- data/sig/bidi2pdf/bidi/interceptor.rbs +31 -0
- data/tasks/coverage.rake +16 -0
- metadata +65 -11
- data/lib/bidi2pdf/bidi/print_parameters_validator.rb +0 -114
- data/sig/bidi2pdf/bidi/print_parameters_validator.rbs +0 -44
- data/sig/bidi2pdf/chrome/chromedriver_downloader.rbs +0 -11
- data/sig/bidi2pdf/chrome/downloader_helper.rbs +0 -9
- data/sig/bidi2pdf/chrome/finder.rbs +0 -27
- data/sig/bidi2pdf/chrome/platform.rbs +0 -13
- data/sig/bidi2pdf/chrome/version_resolver.rbs +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f132ccc5d9db5bb621f7e1a0adfa4150b77b4618542ba27873a602cf9c342a40
|
4
|
+
data.tar.gz: f3df228dc1de555d14baad4347a1b5b8d0bc8b63f88d6adfbcec86f0da571703
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
11
|
+
<!-- generated by git-cliff end -->
|
11
12
|
|
12
|
-
[
|
13
|
+
## [0.1.4] - 2025-04-10
|
13
14
|
|
14
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
23
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
17
|
-
|
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.
|
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
|
-
|
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
|
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 #{
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
})
|
62
|
-
end
|
52
|
+
cmd = Bidi2pdf::Bidi::Commands::CancelAuth.new request: network_id
|
53
|
+
|
54
|
+
client.send_cmd(cmd)
|
63
55
|
|
64
|
-
|
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 "
|
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
|
-
|
24
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
73
|
+
AuthInterceptor.new(
|
83
74
|
context: browsing_context_id,
|
84
75
|
url_patterns: url_patterns,
|
85
|
-
username: username,
|
86
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
105
|
-
|
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
|
-
|
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(
|
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
|
-
|
161
|
-
|
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
|
|
data/lib/bidi2pdf/bidi/client.rb
CHANGED
@@ -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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
33
|
-
|
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(
|
48
|
-
|
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(
|
52
|
-
|
53
|
-
|
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
|
-
|
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(
|
25
|
+
def send_cmd(cmd, store_response: false)
|
16
26
|
id = next_id
|
17
|
-
|
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(
|
26
|
-
id = send_cmd(
|
27
|
-
|
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
|
-
|
30
|
-
|
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
|
39
|
-
@pending_responses
|
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"])
|
44
|
-
@pending_responses
|
45
|
-
|
46
|
-
|
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
|
53
|
-
|
54
|
-
|
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,
|
75
|
-
#
|
76
|
-
|
77
|
-
|
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
|