bidi2pdf 0.1.1 → 0.1.3
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 +13 -0
- data/CHANGELOG.md +40 -2
- data/README.md +10 -0
- data/Rakefile +2 -0
- data/cliff.toml +138 -0
- data/docker/Dockerfile.chromedriver +33 -0
- data/docker/docker-compose.yml +7 -0
- data/docker/entrypoint.sh +3 -0
- data/docker/nginx/default.conf +6 -0
- data/lib/bidi2pdf/bidi/add_headers_interceptor.rb +1 -1
- data/lib/bidi2pdf/bidi/auth_interceptor.rb +1 -1
- data/lib/bidi2pdf/bidi/client.rb +62 -146
- data/lib/bidi2pdf/bidi/command_manager.rb +82 -0
- data/lib/bidi2pdf/bidi/connection_manager.rb +34 -0
- data/lib/bidi2pdf/bidi/session.rb +30 -22
- data/lib/bidi2pdf/chromedriver_manager.rb +40 -16
- data/lib/bidi2pdf/cli.rb +147 -18
- data/lib/bidi2pdf/launcher.rb +19 -6
- data/lib/bidi2pdf/version.rb +1 -1
- data/sig/bidi2pdf/bidi/add_headers_interceptor.rbs +20 -0
- data/sig/bidi2pdf/bidi/auth_interceptor.rbs +17 -0
- data/sig/bidi2pdf/bidi/browser.rbs +38 -0
- data/sig/bidi2pdf/bidi/browser_tab.rbs +42 -0
- data/sig/bidi2pdf/bidi/client.rbs +72 -0
- data/sig/bidi2pdf/bidi/event_manager.rbs +29 -0
- data/sig/bidi2pdf/bidi/network_event.rbs +51 -0
- data/sig/bidi2pdf/bidi/network_events.rbs +55 -0
- data/sig/bidi2pdf/bidi/print_parameters_validator.rbs +44 -0
- data/sig/bidi2pdf/bidi/session.rbs +52 -0
- data/sig/bidi2pdf/bidi/user_context.rbs +50 -0
- data/sig/bidi2pdf/bidi/web_socket_dispatcher.rbs +53 -0
- data/sig/bidi2pdf/chromedriver_manager.rbs +42 -0
- data/sig/bidi2pdf/cli.rbs +21 -0
- data/sig/bidi2pdf/launcher.rbs +38 -0
- data/sig/bidi2pdf/process_tree.rbs +27 -0
- data/sig/bidi2pdf/session_runner.rbs +51 -0
- data/sig/bidi2pdf/utils.rbs +5 -0
- data/sig/vendor/thor.rbs +13 -0
- data/tasks/changelog.rake +29 -0
- data/tasks/generate_rbs.rake +64 -0
- metadata +48 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 715f3587349e1680386f0c485f45b91a6096143c07cc8b531ca7e284a9516615
|
4
|
+
data.tar.gz: 8914fd60cdb04510070dee6b6d7134a94666584583681e26e830824bbf457059
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8e17273531b0131ef9e8b5454154996dbd340d18cbf16a151478b91234e1e53005e0ffb057e16d540f6870b8915bafbdf03831afc2d9b4e9d861494ca13c38f
|
7
|
+
data.tar.gz: d0a4d3b193fc51b681a2e3b54da5cc7cccf05cf578b908144eb7ca5988327f0ee938b07dce88ca826d37889a469e3f32a7da61c68bbb05b5913731aeb0cc9e38
|
data/.rubocop.yml
CHANGED
@@ -45,6 +45,19 @@ Gemspec/DevelopmentDependencies:
|
|
45
45
|
RSpec/InstanceVariable:
|
46
46
|
Enabled: false
|
47
47
|
|
48
|
+
RSpec/BeforeAfterAll:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
RSpec/SpecFilePathFormat:
|
52
|
+
Enabled: true
|
53
|
+
Exclude:
|
54
|
+
- 'spec/acceptance/**/*_spec.rb'
|
55
|
+
|
56
|
+
RSpec/DescribeClass:
|
57
|
+
Enabled: true
|
58
|
+
Exclude:
|
59
|
+
- 'spec/acceptance/**/*_spec.rb'
|
60
|
+
|
48
61
|
plugins:
|
49
62
|
- rubocop-rake
|
50
63
|
- rubocop-rspec
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,46 @@
|
|
1
|
+
<!-- generated by git-cliff start -->
|
2
|
+
|
3
|
+
# Changelog
|
4
|
+
|
5
|
+
All notable changes to this project will be documented in this file.
|
6
|
+
|
7
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
8
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
9
|
+
|
1
10
|
## [Unreleased]
|
2
11
|
|
12
|
+
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.2..HEAD
|
13
|
+
|
14
|
+
<!-- generated by git-cliff end -->
|
15
|
+
|
16
|
+
## [0.1.3] - 2025-04-06
|
17
|
+
|
18
|
+
### 🐛 Fixed
|
19
|
+
|
20
|
+
- Improve zombie process detection and termination logic in Chromedriver manager by @dieter-medium
|
21
|
+
- Add close method for WebSocket connection and update session close logic. Don't leak threads. by @dieter-medium
|
22
|
+
|
23
|
+
## [0.1.2] - 2025-04-05
|
24
|
+
|
25
|
+
### 🔄 Changed
|
26
|
+
|
27
|
+
- Enhance CLI with YAML configuration support and additional options by @dieter-medium
|
28
|
+
|
29
|
+
### 🚀 Added
|
30
|
+
|
31
|
+
- Add CLI tests for rendering and configuration options by @dieter-medium
|
32
|
+
- Add RSpec test for Bidi2pdf version number by @dieter-medium
|
33
|
+
- Add PDF rendering test with custom print parameters and debug output by @dieter-medium
|
34
|
+
- Add be_alive_process matcher and chromedriver manager specs for process management by @dieter-medium
|
35
|
+
- Add RBS type signatures for Bidi2pdf classes and modules by @dieter-medium
|
36
|
+
- Add build status badge to README by @dieter-medium
|
37
|
+
- Add badges for maintainability, gem version, and test coverage in README by @dieter-medium
|
38
|
+
- Add Code Climate coverage reporting to CI workflow by @dieter-medium
|
39
|
+
- Add support for remote ChromeDriver connections and update Docker setup by @dieter-medium
|
40
|
+
|
3
41
|
## [0.1.1] - 2025-04-01
|
4
42
|
|
5
|
-
### Added
|
43
|
+
### 🚀 Added
|
6
44
|
|
7
45
|
- Docker Compose setup with Nginx for authentication examples
|
8
46
|
- Sample configurations for different authentication methods:
|
@@ -10,7 +48,7 @@
|
|
10
48
|
- Cookie-based authentication
|
11
49
|
- API key header authentication
|
12
50
|
|
13
|
-
### Fixed
|
51
|
+
### 🐛 Fixed
|
14
52
|
|
15
53
|
- HTTP cookie handling issues
|
16
54
|
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://github.com/dieter-medium/bidi2pdf/blob/main/.github/workflows/ruby.yml)
|
2
|
+
[](https://codeclimate.com/github/dieter-medium/bidi2pdf/maintainability)
|
3
|
+
[](https://badge.fury.io/rb/bidi2pdf)
|
4
|
+
[](https://codeclimate.com/github/dieter-medium/bidi2pdf/test_coverage)
|
5
|
+
|
1
6
|
# Bidi2pdf
|
2
7
|
|
3
8
|
Bidi2pdf is a Ruby gem that generates high-quality PDFs from web pages using Chrome's BiDi (BiDirectional) protocol. It
|
@@ -94,6 +99,7 @@ docker run -it --rm -v ./output:/reports bidi2pdf \
|
|
94
99
|
### Test it with docker compose
|
95
100
|
|
96
101
|
```bash
|
102
|
+
rake build
|
97
103
|
docker compose -f docker/docker-compose.yml up -d
|
98
104
|
|
99
105
|
# simple example
|
@@ -108,6 +114,8 @@ docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http:
|
|
108
114
|
# cookie example
|
109
115
|
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/cookie/sample.html --cookie "auth=secret" --wait_window_loaded --wait_network_idle --output /reports/cookie.pdf
|
110
116
|
|
117
|
+
# remote chrome example
|
118
|
+
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/cookie/sample.html --remote_browser_url http://remote-chrome:3000/session --cookie "auth=secret" --wait_window_loaded --wait_network_idle --output /reports/remote.pdf
|
111
119
|
|
112
120
|
docker compose -f docker/docker-compose.yml down
|
113
121
|
```
|
@@ -126,6 +134,8 @@ docker compose -f docker/docker-compose.yml down
|
|
126
134
|
| `--wait_window_loaded` | Wait for the window to be fully loaded. You need to set a variable `window.loaded`. See ./spec/fixtures/sample.html |
|
127
135
|
| `--wait_network_idle` | Wait for network to be idle |
|
128
136
|
| `--log_level` | Log level (debug, info, warn, error, fatal) |
|
137
|
+
| `--remote_browser_url` | URL of the remote Chrome instance (default: nil) |
|
138
|
+
| `--default_timeout` | Default timeout for operations (default: 60 seconds) |
|
129
139
|
|
130
140
|
## Development
|
131
141
|
|
data/Rakefile
CHANGED
data/cliff.toml
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# git-cliff ~ configuration file
|
2
|
+
# https://git-cliff.org/docs/configuration
|
3
|
+
|
4
|
+
[changelog]
|
5
|
+
# template for the changelog header
|
6
|
+
header = """
|
7
|
+
<!-- generated by git-cliff start -->
|
8
|
+
# Changelog\n
|
9
|
+
All notable changes to this project will be documented in this file.
|
10
|
+
|
11
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
12
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
|
13
|
+
"""
|
14
|
+
# template for the changelog body
|
15
|
+
# https://keats.github.io/tera/docs/#introduction
|
16
|
+
body = """
|
17
|
+
{%- macro remote_url() -%}
|
18
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
19
|
+
{%- endmacro -%}
|
20
|
+
|
21
|
+
{% if version -%}
|
22
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
23
|
+
{% else -%}
|
24
|
+
## [Unreleased]
|
25
|
+
{% endif -%}
|
26
|
+
|
27
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
28
|
+
### {{ group | upper_first }}
|
29
|
+
{%- for commit in commits %}
|
30
|
+
- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
|
31
|
+
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
|
32
|
+
{% if commit.remote.pr_number %} in \
|
33
|
+
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
34
|
+
{%- endif -%}
|
35
|
+
{% endfor %}
|
36
|
+
{% endfor %}
|
37
|
+
|
38
|
+
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
39
|
+
## New Contributors
|
40
|
+
{%- endif -%}
|
41
|
+
|
42
|
+
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
43
|
+
* @{{ contributor.username }} made their first contribution
|
44
|
+
{%- if contributor.pr_number %} in \
|
45
|
+
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
46
|
+
{%- endif %}
|
47
|
+
{%- endfor %}\n
|
48
|
+
"""
|
49
|
+
# template for the changelog footer
|
50
|
+
footer = """
|
51
|
+
{%- macro remote_url() -%}
|
52
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
53
|
+
{%- endmacro -%}
|
54
|
+
|
55
|
+
{% for release in releases -%}
|
56
|
+
{% if release.version -%}
|
57
|
+
{% if release.previous.version -%}
|
58
|
+
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
59
|
+
{{ self::remote_url() }}/compare/{{ release.previous.version }}..{{ release.version }}
|
60
|
+
{% endif -%}
|
61
|
+
{% else -%}
|
62
|
+
[unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}..HEAD
|
63
|
+
{% endif -%}
|
64
|
+
{% endfor %}
|
65
|
+
<!-- generated by git-cliff end -->
|
66
|
+
"""
|
67
|
+
# remove the leading and trailing whitespace from the templates
|
68
|
+
trim = true
|
69
|
+
|
70
|
+
[git]
|
71
|
+
# parse the commits based on https://www.conventionalcommits.org
|
72
|
+
conventional_commits = true
|
73
|
+
# filter out the commits that are not conventional
|
74
|
+
filter_unconventional = false
|
75
|
+
# regex for preprocessing the commit messages
|
76
|
+
commit_preprocessors = [
|
77
|
+
# remove issue numbers from commits
|
78
|
+
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
79
|
+
]
|
80
|
+
# regex for parsing and grouping commits
|
81
|
+
commit_parsers = [
|
82
|
+
# ✅ Features (additions)
|
83
|
+
{ message = "^feat(?:\\([^)]+\\))?!?:", group = "🚀 Added" },
|
84
|
+
{ message = "^[aA]dd", group = "🚀 Added" },
|
85
|
+
{ message = "^[sS]upport", group = "🚀 Added" },
|
86
|
+
|
87
|
+
# ❌ Removals
|
88
|
+
{ message = "^[rR]emove", group = "🗑️ Removed" },
|
89
|
+
{ message = "^[dD]elete", group = "🗑️ Removed" },
|
90
|
+
|
91
|
+
# 🐛 Fixes
|
92
|
+
{ message = "^fix(?:\\([^)]+\\))?!?:", group = "🐛 Fixed" },
|
93
|
+
{ message = "^[tT]est", group = "🐛 Fixed" },
|
94
|
+
{ message = "^[fF]ix", group = "🐛 Fixed" },
|
95
|
+
|
96
|
+
# 🎨 Refactors
|
97
|
+
{ message = "^refactor(?:\\([^)]+\\))?!?:", group = "🎨 Refactored" },
|
98
|
+
|
99
|
+
# ⚡️ Performance
|
100
|
+
{ message = "^perf(?:\\([^)]+\\))?!?:", group = "⚡️ Performance" },
|
101
|
+
|
102
|
+
# 📝 Docs
|
103
|
+
{ message = "^docs(?:\\([^)]+\\))?!?:", group = "📝 Docs" },
|
104
|
+
|
105
|
+
# 💄 Style (formatting, whitespace, etc.)
|
106
|
+
{ message = "^style(?:\\([^)]+\\))?!?:", group = "💄 Style" },
|
107
|
+
|
108
|
+
# 🧪 Tests
|
109
|
+
{ message = "^test(?:\\([^)]+\\))?!?:", group = "🧪 Tests" },
|
110
|
+
|
111
|
+
# 🔧 Build
|
112
|
+
{ message = "^build(?:\\([^)]+\\))?!?:", group = "🔧 Build" },
|
113
|
+
|
114
|
+
# 🛠️ CI
|
115
|
+
{ message = "^ci(?:\\([^)]+\\))?!?:", skip = true },
|
116
|
+
|
117
|
+
# 🧹 Chores (skip)
|
118
|
+
{ message = "^chore\\(release\\): prepare for", skip = true },
|
119
|
+
{ message = "^chore\\(deps.*\\)", skip = true },
|
120
|
+
{ message = "^chore\\(pr\\)", skip = true },
|
121
|
+
{ message = "^chore\\(pull\\)", skip = true },
|
122
|
+
{ message = "^chore(?:\\([^)]+\\))?!?:", skip = true },
|
123
|
+
{ message = "^\\s*chore", skip = true },
|
124
|
+
|
125
|
+
# ⏪ Reverts
|
126
|
+
{ message = "^revert(?:\\([^)]+\\))?!?:", group = "⏪ Reverted" },
|
127
|
+
|
128
|
+
# 🌀 Catch-all (only if nothing else matched)
|
129
|
+
{ message = "^.*", group = "🔄 Changed" }
|
130
|
+
]
|
131
|
+
|
132
|
+
|
133
|
+
# filter out the commits that are not matched by commit parsers
|
134
|
+
filter_commits = false
|
135
|
+
# sort the tags topologically
|
136
|
+
topo_order = false
|
137
|
+
# sort the commits inside sections by oldest/newest order
|
138
|
+
sort_commits = "newest"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
FROM ruby:3.3
|
2
|
+
|
3
|
+
# Install dependencies
|
4
|
+
RUN apt-get update && \
|
5
|
+
apt-get install -y \
|
6
|
+
chromium \
|
7
|
+
libglib2.0-0 \
|
8
|
+
libnss3 \
|
9
|
+
libxss1 \
|
10
|
+
libasound2 \
|
11
|
+
libatk-bridge2.0-0 \
|
12
|
+
libgtk-3-0 \
|
13
|
+
libdrm2 \
|
14
|
+
curl \
|
15
|
+
unzip \
|
16
|
+
xvfb \
|
17
|
+
&& rm -rf /var/lib/apt/lists/*
|
18
|
+
|
19
|
+
# Create a non-root user
|
20
|
+
RUN groupadd -r appuser && useradd -r -g appuser -m -d /home/appuser appuser
|
21
|
+
|
22
|
+
COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
23
|
+
RUN chmod +x /usr/local/bin/entrypoint.sh
|
24
|
+
|
25
|
+
# Set working directory
|
26
|
+
WORKDIR /app
|
27
|
+
|
28
|
+
# Switch to non-root user
|
29
|
+
USER appuser
|
30
|
+
|
31
|
+
RUN gem install chromedriver-binary && ruby -e 'require "chromedriver/binary"; puts Chromedriver::Binary::ChromedriverDownloader.update'
|
32
|
+
|
33
|
+
CMD ["/usr/local/bin/entrypoint.sh"]
|
data/docker/docker-compose.yml
CHANGED
data/docker/nginx/default.conf
CHANGED
data/lib/bidi2pdf/bidi/client.rb
CHANGED
@@ -6,6 +6,8 @@ require "websocket-client-simple"
|
|
6
6
|
require_relative "web_socket_dispatcher"
|
7
7
|
require_relative "add_headers_interceptor"
|
8
8
|
require_relative "auth_interceptor"
|
9
|
+
require_relative "command_manager"
|
10
|
+
require_relative "connection_manager"
|
9
11
|
|
10
12
|
module Bidi2pdf
|
11
13
|
module Bidi
|
@@ -16,14 +18,6 @@ module Bidi2pdf
|
|
16
18
|
|
17
19
|
def initialize(ws_url)
|
18
20
|
@ws_url = ws_url
|
19
|
-
@id = 0
|
20
|
-
@pending_responses = {}
|
21
|
-
|
22
|
-
@connected = false
|
23
|
-
@connection_mutex = Mutex.new
|
24
|
-
@send_cmd_mutex = Mutex.new
|
25
|
-
@connection_cv = ConditionVariable.new
|
26
|
-
|
27
21
|
@started = false
|
28
22
|
end
|
29
23
|
|
@@ -31,192 +25,114 @@ module Bidi2pdf
|
|
31
25
|
return @socket if started?
|
32
26
|
|
33
27
|
@socket = WebSocket::Client::Simple.connect(ws_url)
|
34
|
-
@dispatcher = WebSocketDispatcher.new(@socket)
|
35
28
|
|
36
|
-
@
|
37
|
-
@
|
29
|
+
@connection_manager = ConnectionManager.new(logger: Bidi2pdf.logger)
|
30
|
+
@command_manager = CommandManager.new(@socket, logger: Bidi2pdf.logger)
|
38
31
|
|
39
|
-
|
32
|
+
dispatcher.on_open { @connection_manager.mark_connected }
|
33
|
+
dispatcher.on_message { |data| handle_response_to_cmd(data) }
|
40
34
|
|
35
|
+
dispatcher.start_listening
|
41
36
|
@started = true
|
42
37
|
|
43
38
|
@socket
|
44
39
|
end
|
45
40
|
|
46
|
-
def started?
|
47
|
-
@started
|
48
|
-
end
|
41
|
+
def started? = @started
|
49
42
|
|
50
43
|
def wait_until_open(timeout: Bidi2pdf.default_timeout)
|
51
|
-
@
|
52
|
-
unless @connected
|
53
|
-
Bidi2pdf.logger.debug "Waiting for WebSocket connection to open"
|
54
|
-
@connection_cv.wait(@connection_mutex, timeout)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
raise "WebSocket connection did not open in time" unless @connected
|
59
|
-
|
60
|
-
Bidi2pdf.logger.debug "WebSocket connection is open"
|
44
|
+
@connection_manager.wait_until_open(timeout: timeout)
|
61
45
|
end
|
62
46
|
|
63
47
|
def send_cmd(method, params = {})
|
64
|
-
|
65
|
-
payload = {
|
66
|
-
id: cmd_id,
|
67
|
-
method: method,
|
68
|
-
params: params
|
69
|
-
}
|
70
|
-
|
71
|
-
Bidi2pdf.logger.debug "Sending command: #{redact_sensitive_fields(payload).inspect}"
|
72
|
-
|
73
|
-
@socket.send(payload.to_json)
|
74
|
-
end
|
48
|
+
@command_manager.send_cmd(method, params)
|
75
49
|
end
|
76
50
|
|
77
|
-
|
78
|
-
def send_cmd_and_wait(method, params = {}, timeout: Bidi2pdf.default_timeout)
|
51
|
+
def send_cmd_and_wait(method, params = {}, timeout: Bidi2pdf.default_timeout, &block)
|
79
52
|
timed("Command #{method}") do
|
80
|
-
|
81
|
-
queue = @pending_responses[id]
|
82
|
-
|
83
|
-
begin
|
84
|
-
response = queue.pop(true)
|
85
|
-
rescue ThreadError
|
86
|
-
response = queue.pop(timeout: timeout)
|
87
|
-
end
|
88
|
-
|
89
|
-
if response.nil?
|
90
|
-
# rubocop:disable Layout/LineLength
|
91
|
-
Bidi2pdf.logger.error "Timeout waiting for response to command #{id}, cmd: #{method}, params: #{redact_sensitive_fields(params).inspect}"
|
92
|
-
# rubocop:enable Layout/LineLength
|
93
|
-
|
94
|
-
raise "Timeout waiting for response to command ID #{id}"
|
95
|
-
end
|
96
|
-
|
97
|
-
raise "Error response: #{response["error"]}" if response["error"]
|
98
|
-
|
99
|
-
result = response
|
100
|
-
|
101
|
-
result = yield response if block_given?
|
102
|
-
|
103
|
-
result
|
104
|
-
ensure
|
105
|
-
@pending_responses.delete(id)
|
53
|
+
@command_manager.send_cmd_and_wait(method, params, timeout: timeout, &block)
|
106
54
|
end
|
107
55
|
end
|
108
56
|
|
109
|
-
|
110
|
-
|
111
|
-
# Event API for external consumers
|
112
|
-
def on_message(&block) = @dispatcher.on_message(&block)
|
57
|
+
def on_message(&block) = dispatcher.on_message(&block)
|
113
58
|
|
114
|
-
def on_open(&block) =
|
59
|
+
def on_open(&block) = dispatcher.on_open(&block)
|
115
60
|
|
116
|
-
def on_close(&block) =
|
61
|
+
def on_close(&block) = dispatcher.on_close(&block)
|
117
62
|
|
118
|
-
def on_error(&block) =
|
63
|
+
def on_error(&block) = dispatcher.on_error(&block)
|
119
64
|
|
120
65
|
def on_event(*names, &block)
|
121
|
-
names.each
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
send_cmd "session.subscribe", { events: names } if names.any?
|
66
|
+
names.each { |name| dispatcher.on_event(name, &block) }
|
67
|
+
send_cmd("session.subscribe", { events: names }) if names.any?
|
126
68
|
end
|
127
69
|
|
128
|
-
def remove_message_listener(block) =
|
70
|
+
def remove_message_listener(block) = dispatcher.remove_message_listener(block)
|
129
71
|
|
130
72
|
def remove_event_listener(*names, &block)
|
131
|
-
names.each
|
132
|
-
@dispatcher.remove_event_listener(event_name, block)
|
133
|
-
end
|
73
|
+
names.each { |event_name| dispatcher.remove_event_listener(event_name, block) }
|
134
74
|
end
|
135
75
|
|
136
|
-
def add_headers_interceptor(
|
137
|
-
|
138
|
-
url_patterns:,
|
139
|
-
headers:
|
140
|
-
)
|
141
|
-
send_cmd_and_wait("network.addIntercept", {
|
76
|
+
def add_headers_interceptor(context:, url_patterns:, headers:)
|
77
|
+
add_interceptor(
|
142
78
|
context: context,
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
AddHeadersInterceptor.new(id, headers, self).tap do |interceptor|
|
150
|
-
on_event "network.beforeRequestSent", &interceptor.method(:handle_event)
|
151
|
-
end
|
152
|
-
end
|
79
|
+
url_patterns: url_patterns,
|
80
|
+
phase: "beforeRequestSent",
|
81
|
+
event: "network.beforeRequestSent",
|
82
|
+
interceptor_class: AddHeadersInterceptor,
|
83
|
+
extra_args: { headers: headers }
|
84
|
+
)
|
153
85
|
end
|
154
86
|
|
155
|
-
def add_auth_interceptor(
|
156
|
-
|
157
|
-
url_patterns:,
|
158
|
-
username:,
|
159
|
-
password:
|
160
|
-
)
|
161
|
-
send_cmd_and_wait("network.addIntercept", {
|
87
|
+
def add_auth_interceptor(context:, url_patterns:, username:, password:)
|
88
|
+
add_interceptor(
|
162
89
|
context: context,
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
AuthInterceptor.new(id, username, password, self).tap do |interceptor|
|
170
|
-
on_event "network.authRequired", &interceptor.method(:handle_event)
|
171
|
-
end
|
172
|
-
end
|
90
|
+
url_patterns: url_patterns,
|
91
|
+
phase: "authRequired",
|
92
|
+
event: "network.authRequired",
|
93
|
+
interceptor_class: AuthInterceptor,
|
94
|
+
extra_args: { username: username, password: password }
|
95
|
+
)
|
173
96
|
end
|
174
97
|
|
175
|
-
|
176
|
-
|
177
|
-
def next_id
|
178
|
-
cmd_id = nil
|
179
|
-
|
180
|
-
@send_cmd_mutex.synchronize do
|
181
|
-
@id += 1
|
182
|
-
cmd_id = @id
|
183
|
-
@pending_responses[cmd_id] = Queue.new
|
184
|
-
end
|
98
|
+
def close
|
99
|
+
return unless @socket
|
185
100
|
|
186
|
-
|
101
|
+
Bidi2pdf.logger.debug "Closing WebSocket connection"
|
102
|
+
@socket&.close
|
103
|
+
@socket = nil
|
104
|
+
@started = false
|
187
105
|
end
|
188
106
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
end
|
107
|
+
private
|
108
|
+
|
109
|
+
def dispatcher
|
110
|
+
@dispatcher ||= WebSocketDispatcher.new(@socket)
|
194
111
|
end
|
195
112
|
|
196
113
|
def handle_response_to_cmd(data)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
114
|
+
handled = @command_manager.handle_response(data)
|
115
|
+
return if handled
|
116
|
+
|
117
|
+
if data["error"]
|
118
|
+
Bidi2pdf.logger.error "Error response: #{data["error"].inspect}"
|
201
119
|
else
|
202
120
|
Bidi2pdf.logger.warn "Unknown response: #{data.inspect}"
|
203
121
|
end
|
204
122
|
end
|
205
123
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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))
|
215
135
|
end
|
216
|
-
when Array
|
217
|
-
obj.map { |item| redact_sensitive_fields(item, sensitive_keys) }
|
218
|
-
else
|
219
|
-
obj
|
220
136
|
end
|
221
137
|
end
|
222
138
|
end
|