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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e85e284d37a585d84857db9f02a93bc7d9aacbf5c026da8ab06559fdff121f16
4
- data.tar.gz: 76dbe3a44dee51d1c520dc0bd4d282790309efab1ecc5567fcee585b064660f1
3
+ metadata.gz: cd4d66743b0fba81220c113a7c99a09a94b216acee4d734a6e69ff76d27378b7
4
+ data.tar.gz: 60a8eb9469960e376c0d71a51bf801863e60d7a52e935578920ce227d8397e2b
5
5
  SHA512:
6
- metadata.gz: e66d36468423be0212176d8d40df94c5e10ba3d5f23663041b502c0d481580f94f12f308deaffac18b25fef1cc0bd644dac110e4d98ce365574ef6aa53dc4784
7
- data.tar.gz: b147ca301c15e728b9bf6e3682bcf975f7f105adadd77d62db9a6a68fa8a05cd73533562f198b0825abc99f45ee73c04cf50aa2205d840d0181f3b599e9f7093
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
 
@@ -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.
@@ -109,7 +109,11 @@ module Browserctl
109
109
  end
110
110
 
111
111
  def format_params(defn)
112
- defn.param_defs.transform_values { |p| { required: p.required, secret: p.secret, default: p.default } }
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
- raise SecretResolverError,
21
- "'#{scheme}://' resolver is not available in this environment"
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 }
@@ -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
- raise Browserctl::Error, "session encryption requires macOS Keychain (darwin only)" unless keychain_available?
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Browserctl
4
- VERSION = "0.8.1"
4
+ VERSION = "0.8.3"
5
5
  end
@@ -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
- raise WorkflowError, res[:error] unless fallback
76
+ if res[:error]
77
+ raise WorkflowError, res[:error] unless fallback_name
75
78
 
76
- invoke(fallback.to_s)
77
- res2 = @client.session_load(session_name)
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
- res2
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: browserctl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick