bidi2pdf 0.1.21 → 0.1.22
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/CHANGELOG.md +16 -2
- data/README.md +3 -0
- data/lib/bidi2pdf/bidi/browser_tab.rb +19 -2
- data/lib/bidi2pdf/bidi/network_events.rb +13 -4
- data/lib/bidi2pdf/notifications/json_subscriber.rb +3 -2
- data/lib/bidi2pdf/notifications/logging_subscriber.rb +7 -3
- data/lib/bidi2pdf/schema.rb +2 -1
- data/lib/bidi2pdf/version.rb +1 -1
- data/sig/bidi2pdf/bidi/browser_tab.rbs +12 -1
- data/sig/bidi2pdf/bidi/network_events.rbs +8 -1
- data/sig/bidi2pdf/notifications/json_subscriber.rbs +2 -1
- data/sig/bidi2pdf/schema.rbs +1 -1
- data/sig/bidi2pdf/version.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '008d5b0569ba4b7f8bbcd4092da72ce14e5d0e42589a97813f7c64c8bc6a0d9f'
|
|
4
|
+
data.tar.gz: 8266f91bfa7606d843bed392cee203060cc3b13d98435cc9a0c4a679b5041285
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27ce2475fa8d30bc9c5447353767c6d2c06db59f57daa7fab7e7a337e26703cc1a0df04439d2b8b68455e5a872138775321a7f31988beb02dba47df0dd06d48d
|
|
7
|
+
data.tar.gz: acb216b0c656916ee8e8ea797b1fccd026c67fa6260ac5f230f1c44190ffb00455fd83efb5238826c97af0f0051cf68a7335d09e51e0aaa25b85c2efdc802290
|
data/CHANGELOG.md
CHANGED
|
@@ -8,10 +8,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
-
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
11
|
+
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.22..HEAD
|
|
12
12
|
|
|
13
13
|
<!-- generated by git-cliff end -->
|
|
14
14
|
|
|
15
|
+
## [0.1.22] - 2026-10-11
|
|
16
|
+
|
|
17
|
+
### 🐛 Fixed
|
|
18
|
+
- Report a network-idle timeout as a timeout
|
|
19
|
+
- Let the network-idle wait time out cleanly
|
|
20
|
+
|
|
21
|
+
### 🔄 Changed
|
|
22
|
+
- Merge pull request #164 from dieter-medium/fix/network-idle-timeout
|
|
23
|
+
- Merge pull request #163 from dieter-medium/feat/disable-script-execution
|
|
24
|
+
|
|
25
|
+
### 🚀 Added
|
|
26
|
+
- Let a tab switch off script execution
|
|
27
|
+
|
|
15
28
|
## [0.1.21] - 2026-10-09
|
|
16
29
|
|
|
17
30
|
### 🐛 Fixed
|
|
@@ -555,7 +568,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
555
568
|
|
|
556
569
|
### 🔄 Released
|
|
557
570
|
|
|
558
|
-
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
571
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.22..HEAD)
|
|
572
|
+
- [0.1.22](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.21..v0.1.22)
|
|
559
573
|
- [0.1.21](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.20..v0.1.21)
|
|
560
574
|
- [0.1.20](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.19..v0.1.20)
|
|
561
575
|
- [0.1.19](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.18..v0.1.19)
|
data/README.md
CHANGED
|
@@ -347,6 +347,9 @@ tab.navigate_to "https://example.com/invoice/14432423"
|
|
|
347
347
|
# or inline
|
|
348
348
|
# tab.inject_style content: "body { background-color: red; }"
|
|
349
349
|
|
|
350
|
+
# Printing HTML you do not trust? Switch its scripts off before navigating:
|
|
351
|
+
# tab.disable_script_execution
|
|
352
|
+
|
|
350
353
|
tab.wait_until_network_idle
|
|
351
354
|
tab.print("my.pdf")
|
|
352
355
|
|
|
@@ -334,15 +334,20 @@ module Bidi2pdf
|
|
|
334
334
|
end
|
|
335
335
|
end
|
|
336
336
|
|
|
337
|
-
# Waits until the network is idle in the browser tab.
|
|
337
|
+
# Waits until the network is idle in the browser tab. A timeout does not raise: the
|
|
338
|
+
# network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
|
|
339
|
+
# page as idle.
|
|
338
340
|
#
|
|
339
341
|
# @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
|
|
340
342
|
# @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
|
|
343
|
+
# @return [Symbol] :idle or :timeout
|
|
341
344
|
def wait_until_network_idle(timeout: 10, poll_interval: 0.1)
|
|
342
345
|
Bidi2pdf.notification_service.instrument("network_idle.bidi2pdf") do |instrumentation_payload|
|
|
343
|
-
network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
|
|
346
|
+
status = network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
|
|
344
347
|
|
|
345
348
|
instrumentation_payload[:requests] = network_events.all_events.dup
|
|
349
|
+
instrumentation_payload[:timed_out] = status == :timeout
|
|
350
|
+
status
|
|
346
351
|
end
|
|
347
352
|
end
|
|
348
353
|
|
|
@@ -496,6 +501,18 @@ module Bidi2pdf
|
|
|
496
501
|
client.send_cmd_and_wait(cmd) { |response| response }
|
|
497
502
|
end
|
|
498
503
|
|
|
504
|
+
# Switches JavaScript off (or on again) for this tab, through Chrome's own
|
|
505
|
+
# Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
|
|
506
|
+
# navigating: it holds for every later navigation in the tab, so a page printed from untrusted
|
|
507
|
+
# HTML runs none of its scripts.
|
|
508
|
+
#
|
|
509
|
+
# @param [Boolean] disabled Defaults to true.
|
|
510
|
+
def disable_script_execution(disabled: true)
|
|
511
|
+
cmd = Commands::CdpSendCommand.new(method: "Emulation.setScriptExecutionDisabled", params: { value: disabled },
|
|
512
|
+
session: cdp_session)
|
|
513
|
+
client.send_cmd_and_wait(cmd) { |response| response }
|
|
514
|
+
end
|
|
515
|
+
|
|
499
516
|
private
|
|
500
517
|
|
|
501
518
|
def build_command_and_extractor(print_options)
|
|
@@ -95,24 +95,33 @@ module Bidi2pdf
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# Waits until no request is in flight, or until +timeout+ seconds passed.
|
|
99
|
+
#
|
|
100
|
+
# @return [Symbol] :idle when every request completed, :timeout when some were still open -
|
|
101
|
+
# it never raises for a timeout, so a caller decides whether a busy page is good enough.
|
|
98
102
|
def wait_until_network_idle(timeout: 10, poll_interval: 0.01)
|
|
99
103
|
start_time = Time.now
|
|
100
104
|
|
|
101
105
|
loop do
|
|
102
106
|
unless events.values.any?(&:in_progress?)
|
|
103
107
|
Bidi2pdf.logger.debug "✅ All network events completed."
|
|
104
|
-
|
|
108
|
+
return :idle
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
if Time.now - start_time > timeout
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
break
|
|
112
|
+
log_idle_timeout
|
|
113
|
+
return :timeout
|
|
111
114
|
end
|
|
112
115
|
|
|
113
116
|
sleep(poll_interval)
|
|
114
117
|
end
|
|
115
118
|
end
|
|
119
|
+
|
|
120
|
+
# Names the requests still open when the wait gives up - it carries on, it does not raise.
|
|
121
|
+
def log_idle_timeout
|
|
122
|
+
in_progress = events.values.select(&:in_progress?)
|
|
123
|
+
Bidi2pdf.logger.warn "⏰ Timeout while waiting for network events to complete. Still in progress: #{in_progress.map(&:id)}"
|
|
124
|
+
end
|
|
116
125
|
end
|
|
117
126
|
end
|
|
118
127
|
end
|
|
@@ -53,8 +53,9 @@ module Bidi2pdf
|
|
|
53
53
|
write(event: "page_loaded")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
# A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
|
|
57
|
+
def on_network_idle(event)
|
|
58
|
+
write(event: event.payload[:timed_out] ? "network_idle_timeout" : "network_idle")
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def on_console(event)
|
|
@@ -61,7 +61,7 @@ module Bidi2pdf
|
|
|
61
61
|
def network_idle(event)
|
|
62
62
|
return unless logger.info?
|
|
63
63
|
|
|
64
|
-
requests = event.payload[:requests]
|
|
64
|
+
requests = event.payload[:requests] || []
|
|
65
65
|
transfered = requests.map { |request| request.bytes_received || 0 }.sum
|
|
66
66
|
status_counts = requests
|
|
67
67
|
.group_by { |evt| evt.http_status_code || 0 }
|
|
@@ -70,8 +70,12 @@ module Bidi2pdf
|
|
|
70
70
|
.map { |code, count| "#{code}: #{count}" }
|
|
71
71
|
.join(", ")
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
summary = "#{requests.size} requests, transferred #{transfered} bytes (status codes: #{status_counts})"
|
|
74
|
+
if event.payload[:timed_out]
|
|
75
|
+
logger.warn "Network was still busy after #{event.duration.round(1)}ms (timed out), #{summary}"
|
|
76
|
+
else
|
|
77
|
+
logger.info "Network was idle after #{event.duration.round(1)}ms, #{summary}"
|
|
78
|
+
end
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
data/lib/bidi2pdf/schema.rb
CHANGED
|
@@ -196,7 +196,8 @@ module Bidi2pdf
|
|
|
196
196
|
"properties" => {
|
|
197
197
|
"schema_version" => { "const" => 1 },
|
|
198
198
|
"t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" },
|
|
199
|
-
"event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle console print screenshot result]
|
|
199
|
+
"event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle network_idle_timeout console print screenshot result],
|
|
200
|
+
"description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" },
|
|
200
201
|
"url" => { "type" => "string", "description" => "present on a navigate event" },
|
|
201
202
|
"level" => { "type" => "string", "description" => "present on a console event" },
|
|
202
203
|
"text" => { "type" => "string", "description" => "present on a console event" },
|
data/lib/bidi2pdf/version.rb
CHANGED
|
@@ -169,10 +169,13 @@ module Bidi2pdf
|
|
|
169
169
|
# @return [Object] The result from the style creation promise.
|
|
170
170
|
def inject_style: (?url: untyped?, ?content: untyped?, ?id: untyped?) -> untyped
|
|
171
171
|
|
|
172
|
-
# Waits until the network is idle in the browser tab.
|
|
172
|
+
# Waits until the network is idle in the browser tab. A timeout does not raise: the
|
|
173
|
+
# network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
|
|
174
|
+
# page as idle.
|
|
173
175
|
#
|
|
174
176
|
# @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
|
|
175
177
|
# @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
|
|
178
|
+
# @return [Symbol] :idle or :timeout
|
|
176
179
|
def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> untyped
|
|
177
180
|
|
|
178
181
|
# Waits until the page is fully loaded in the browser tab.
|
|
@@ -227,6 +230,14 @@ module Bidi2pdf
|
|
|
227
230
|
# @param [Float, nil] device_pixel_ratio Defaults to nil (browser default).
|
|
228
231
|
def set_viewport: (width: untyped, height: untyped, ?device_pixel_ratio: untyped?) -> untyped
|
|
229
232
|
|
|
233
|
+
# Switches JavaScript off (or on again) for this tab, through Chrome's own
|
|
234
|
+
# Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
|
|
235
|
+
# navigating: it holds for every later navigation in the tab, so a page printed from untrusted
|
|
236
|
+
# HTML runs none of its scripts.
|
|
237
|
+
#
|
|
238
|
+
# @param [Boolean] disabled Defaults to true.
|
|
239
|
+
def disable_script_execution: (?disabled: bool) -> untyped
|
|
240
|
+
|
|
230
241
|
private
|
|
231
242
|
|
|
232
243
|
def build_command_and_extractor: (untyped print_options) -> ::Array[untyped]
|
|
@@ -24,7 +24,14 @@ module Bidi2pdf
|
|
|
24
24
|
|
|
25
25
|
def log_network_traffic: (?format: ::Symbol) -> untyped
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# Waits until no request is in flight, or until +timeout+ seconds passed.
|
|
28
|
+
#
|
|
29
|
+
# @return [Symbol] :idle when every request completed, :timeout when some were still open -
|
|
30
|
+
# it never raises for a timeout, so a caller decides whether a busy page is good enough.
|
|
31
|
+
def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> (:idle | :timeout | untyped)
|
|
32
|
+
|
|
33
|
+
# Names the requests still open when the wait gives up - it carries on, it does not raise.
|
|
34
|
+
def log_idle_timeout: () -> untyped
|
|
28
35
|
end
|
|
29
36
|
end
|
|
30
37
|
end
|
|
@@ -31,7 +31,8 @@ module Bidi2pdf
|
|
|
31
31
|
|
|
32
32
|
def on_page_loaded: (untyped _event) -> untyped
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
# A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
|
|
35
|
+
def on_network_idle: (untyped event) -> untyped
|
|
35
36
|
|
|
36
37
|
def on_console: (untyped event) -> untyped
|
|
37
38
|
|
data/sig/bidi2pdf/schema.rbs
CHANGED
|
@@ -26,7 +26,7 @@ module Bidi2pdf
|
|
|
26
26
|
|
|
27
27
|
MANIFEST: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf render manifest", "description" => "--manifest FILE output", "type" => "object", "required" => ::Array["schema_version" | "bidi2pdf_version" | "created_at" | "input" | "output" | "navigation" | "render"], "properties" => { "schema_version" => { "const" => 1 }, "bidi2pdf_version" => { "type" => "string" }, "created_at" => { "type" => "string", "format" => "date-time" }, "input" => { "type" => "object", "required" => ::Array["type"], "properties" => { "type" => { "type" => "string", "enum" => ::Array["url" | "html_file" | "stdin"] }, "url" => { "type" => ::Array["string" | "null"] } } }, "output" => { "type" => "object", "properties" => { "path" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] } } }, "browser" => { "type" => "object" }, "navigation" => untyped, "render" => { "type" => "object", "properties" => { "duration_ms" => { "type" => ::Array["integer" | "null"] } } }, "console" => untyped, "network_failures" => untyped, "warnings" => untyped, "headers" => { "type" => "object", "description" => "present only when custom headers were given; secret-bearing values are redacted" } } }
|
|
28
28
|
|
|
29
|
-
EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "console" | "print" | "screenshot" | "result"] }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
|
|
29
|
+
EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "network_idle_timeout" | "console" | "print" | "screenshot" | "result"], "description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
|
|
30
30
|
|
|
31
31
|
RECIPE_SOURCE: { "description" => "exactly one of url, file, stdin", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["url"], "properties" => { "url" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["file"], "properties" => { "file" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["stdin"], "properties" => { "stdin" => { "const" => true } }, "additionalProperties" => false }] }
|
|
32
32
|
|
data/sig/bidi2pdf/version.rbs
CHANGED