browserctl 0.8.1 → 0.8.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/CHANGELOG.md +19 -0
- data/lib/browserctl/client.rb +2 -2
- data/lib/browserctl/runner.rb +5 -1
- data/lib/browserctl/secret_resolver_registry.rb +6 -2
- data/lib/browserctl/server/handlers/session.rb +2 -1
- data/lib/browserctl/session.rb +6 -1
- data/lib/browserctl/version.rb +1 -1
- data/lib/browserctl/workflow.rb +53 -9
- 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: cd4d66743b0fba81220c113a7c99a09a94b216acee4d734a6e69ff76d27378b7
|
|
4
|
+
data.tar.gz: 60a8eb9469960e376c0d71a51bf801863e60d7a52e935578920ce227d8397e2b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ceed28ff7dd8603bbaaee3ddaa5f6e65669a623aca423633a3ce08f64584064d37a400e461f8828260f245c34bba509452431c4af8ce2c672764778337a51075
|
|
7
|
+
data.tar.gz: 932c20041989b25fc3fc7d20aa23909edd1651a00500071fbe1ceb4b38edb7c0d64c033cd267f5a4ad240fb3d3f60020320f94f5c45ad9912f79a1960549d7e0
|
data/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,25 @@ All notable changes to this project will be documented in this file.
|
|
|
10
10
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
11
11
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
12
12
|
|
|
13
|
+
## [0.8.3](https://github.com/patrick204nqh/browserctl/compare/v0.8.2...v0.8.3) (2026-04-29)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* expired_if: on load_session — detect stale sessions and auto-recover ([#67](https://github.com/patrick204nqh/browserctl/issues/67)) ([c6a4674](https://github.com/patrick204nqh/browserctl/commit/c6a467439835373165b1323c4aad6144388533be))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Miscellaneous Chores
|
|
22
|
+
|
|
23
|
+
* pin next release to 0.8.3 ([#69](https://github.com/patrick204nqh/browserctl/issues/69)) ([a017e3c](https://github.com/patrick204nqh/browserctl/commit/a017e3c3b464060ced2ecd4d76fa04049f40e46a))
|
|
24
|
+
|
|
25
|
+
## [0.8.2](https://github.com/patrick204nqh/browserctl/compare/v0.8.1...v0.8.2) (2026-04-29)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* v0.8.2 UX polish — describe secret_ref, fallback hint, cross-platform encrypt guidance ([#65](https://github.com/patrick204nqh/browserctl/issues/65)) ([2fe5ea4](https://github.com/patrick204nqh/browserctl/commit/2fe5ea4da0997dc2ab2c3a0696fc819e3fffca40))
|
|
31
|
+
|
|
13
32
|
## [0.8.1](https://github.com/patrick204nqh/browserctl/compare/v0.8.0...v0.8.1) (2026-04-29)
|
|
14
33
|
|
|
15
34
|
|
data/lib/browserctl/client.rb
CHANGED
|
@@ -270,8 +270,8 @@ module Browserctl
|
|
|
270
270
|
# Saves the current browser state (cookies, localStorage, open pages) to a named session.
|
|
271
271
|
# @param session_name [String] name for the saved session
|
|
272
272
|
# @return [Hash] `{ ok: true, path:, pages: N, cookies: N }` or `{ error: }`
|
|
273
|
-
def session_save(session_name)
|
|
274
|
-
call("session_save", session_name: session_name)
|
|
273
|
+
def session_save(session_name, encrypt: false)
|
|
274
|
+
call("session_save", session_name: session_name, encrypt: encrypt)
|
|
275
275
|
end
|
|
276
276
|
|
|
277
277
|
# Restores a previously saved session into the running daemon.
|
data/lib/browserctl/runner.rb
CHANGED
|
@@ -109,7 +109,11 @@ module Browserctl
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def format_params(defn)
|
|
112
|
-
defn.param_defs.transform_values
|
|
112
|
+
defn.param_defs.transform_values do |p|
|
|
113
|
+
entry = { required: p.required, secret: p.secret, default: p.default }
|
|
114
|
+
entry[:secret_ref] = p.secret_ref if p.secret_ref
|
|
115
|
+
entry
|
|
116
|
+
end
|
|
113
117
|
end
|
|
114
118
|
end
|
|
115
119
|
end
|
|
@@ -16,9 +16,13 @@ module Browserctl
|
|
|
16
16
|
scheme, reference = secret_ref.split("://", 2)
|
|
17
17
|
resolver = @mutex.synchronize { @registry[scheme] }
|
|
18
18
|
raise SecretResolverError, "unknown secret resolver scheme '#{scheme}'" unless resolver
|
|
19
|
+
|
|
19
20
|
unless resolver.available?
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
msg = "'#{scheme}://' resolver is not available in this environment"
|
|
22
|
+
if scheme == "keychain"
|
|
23
|
+
msg += "\n Use env://YOUR_VAR_NAME to source secrets from environment variables instead."
|
|
24
|
+
end
|
|
25
|
+
raise SecretResolverError, msg
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
resolver.resolve(reference)
|
|
@@ -32,7 +32,8 @@ module Browserctl
|
|
|
32
32
|
created_at: now, updated_at: now, pages: pages_meta },
|
|
33
33
|
cookies: cookies,
|
|
34
34
|
local_storage: local_storage,
|
|
35
|
-
session_storage: {}
|
|
35
|
+
session_storage: {},
|
|
36
|
+
encrypt: req[:encrypt] || false
|
|
36
37
|
)
|
|
37
38
|
{ ok: true, path: Browserctl::Session.path(req[:session_name]),
|
|
38
39
|
pages: pages_meta.length, cookies: cookies.length }
|
data/lib/browserctl/session.rb
CHANGED
|
@@ -39,7 +39,12 @@ module Browserctl
|
|
|
39
39
|
def self.prepare_encryption(session_name, metadata, encrypt)
|
|
40
40
|
return [nil, metadata] unless encrypt
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
unless keychain_available?
|
|
43
|
+
raise Browserctl::Error,
|
|
44
|
+
"session encryption requires macOS Keychain (darwin only)\n " \
|
|
45
|
+
"For Linux/CI, omit --encrypt and rely on 0o600 file permissions,\n " \
|
|
46
|
+
"or use BROWSERCTL_EXPORT_PASSPHRASE with session export --encrypt for portable archives."
|
|
47
|
+
end
|
|
43
48
|
|
|
44
49
|
key = SecureRandom.bytes(32)
|
|
45
50
|
keychain_store(session_name, key)
|
data/lib/browserctl/version.rb
CHANGED
data/lib/browserctl/workflow.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "timeout"
|
|
|
4
4
|
require_relative "client"
|
|
5
5
|
require_relative "errors"
|
|
6
6
|
require_relative "secret_resolvers"
|
|
7
|
+
require_relative "session"
|
|
7
8
|
|
|
8
9
|
module Browserctl
|
|
9
10
|
ParamDef = Struct.new(:name, :required, :secret, :default, :secret_ref, keyword_init: true)
|
|
@@ -67,20 +68,21 @@ module Browserctl
|
|
|
67
68
|
res
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
def load_session(session_name, fallback: nil)
|
|
71
|
+
def load_session(session_name, fallback: nil, expired_if: nil)
|
|
72
|
+
validate_expired_if!(expired_if)
|
|
73
|
+
fallback_name = fallback&.to_s
|
|
71
74
|
res = @client.session_load(session_name)
|
|
72
|
-
return res unless res[:error]
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
if res[:error]
|
|
77
|
+
raise WorkflowError, res[:error] unless fallback_name
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if res2[:error]
|
|
79
|
-
raise WorkflowError,
|
|
80
|
-
"session '#{session_name}' still unavailable after running fallback '#{fallback}'"
|
|
79
|
+
invoke(fallback_name)
|
|
80
|
+
return load_after_fallback(session_name, fallback_name)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
return res if expired_if.nil? || !call_expired_if(expired_if, session_name)
|
|
84
|
+
|
|
85
|
+
recover_expired_session(session_name, fallback_name, expired_if)
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
def list_sessions
|
|
@@ -104,6 +106,48 @@ module Browserctl
|
|
|
104
106
|
|
|
105
107
|
private
|
|
106
108
|
|
|
109
|
+
def validate_expired_if!(expired_if)
|
|
110
|
+
return unless expired_if && !expired_if.lambda?
|
|
111
|
+
|
|
112
|
+
raise ArgumentError,
|
|
113
|
+
"expired_if: must be a lambda (-> { }), not a Proc — " \
|
|
114
|
+
"bare return inside a Proc unwinds the caller"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def call_expired_if(expired_if, session_name)
|
|
118
|
+
expired_if.call
|
|
119
|
+
rescue WorkflowError, StandardError => e
|
|
120
|
+
raise WorkflowError, "expired_if check failed for session '#{session_name}': #{e.message}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def recover_expired_session(session_name, fallback_name, expired_if)
|
|
124
|
+
unless fallback_name
|
|
125
|
+
raise WorkflowError,
|
|
126
|
+
"session '#{session_name}' is expired; provide fallback: to auto-recover"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
invoke(fallback_name)
|
|
130
|
+
res = load_after_fallback(session_name, fallback_name)
|
|
131
|
+
|
|
132
|
+
if call_expired_if(expired_if, session_name)
|
|
133
|
+
raise WorkflowError,
|
|
134
|
+
"session '#{session_name}' still expired after running fallback '#{fallback_name}'"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
res
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def load_after_fallback(session_name, fallback)
|
|
141
|
+
res = @client.session_load(session_name)
|
|
142
|
+
return res unless res[:error]
|
|
143
|
+
|
|
144
|
+
msg = "session '#{session_name}' still unavailable after running fallback '#{fallback}'"
|
|
145
|
+
unless Session.exist?(session_name)
|
|
146
|
+
msg += "\n Hint: '#{fallback}' did not call save_session(\"#{session_name}\") — add it as the last step."
|
|
147
|
+
end
|
|
148
|
+
raise WorkflowError, msg
|
|
149
|
+
end
|
|
150
|
+
|
|
107
151
|
def invoke_stack
|
|
108
152
|
@invoke_stack ||= []
|
|
109
153
|
end
|