Module: Browserctl::State
- Defined in:
- lib/browserctl/state.rb,
lib/browserctl/state/bundle.rb,
lib/browserctl/state/mutator.rb,
lib/browserctl/state/transport.rb,
lib/browserctl/state/transports/s3.rb,
lib/browserctl/state/transports/file.rb,
lib/browserctl/state/transports/one_password.rb
Overview
Top-level state store: a single .bctl bundle per name under ~/.browserctl/state/<name>.bctl. Wraps the Bundle codec with on-disk naming, validation, and a small inventory API used by ‘state list/info`.
Data shape inside a bundle:
manifest = {
name: String,
version: 1, # bundle schema version
producer: "browserctl/<gem-ver>",
created_at: ISO-8601,
origins: [String, ...],
flow: String | nil, # bound flow name, for `state rotate`
flow_version: String | nil,
expires_at: ISO-8601 | nil, # earliest cookie expiry
encrypted: Boolean
}
payload = {
cookies: [Hash, ...],
local_storage: { origin => { key => value } },
session_storage: { origin => { key => value } }
}
Defined Under Namespace
Modules: Transport, Transports Classes: Bundle, Mutator, Payload
Constant Summary collapse
- BASE_DIR =
File.join(BROWSERCTL_DIR, "state")
- SAFE_NAME =
/\A[a-zA-Z0-9_-]{1,64}\z/- EXTENSION =
".bctl"- MANIFEST_VERSION =
1
Class Method Summary collapse
-
.all ⇒ Object
Read manifests for all stored bundles.
- .delete(name) ⇒ Object
- .exist?(name) ⇒ Boolean
-
.export(name, destination) ⇒ Object
Copies the on-disk .bctl bundle to a transport-addressable destination (file path, s3://bucket/key, op://Vault/Item, or any registered scheme).
-
.import(source, name: nil) ⇒ Object
Pulls a bundle from a transport-addressable source and stores it as a local state.
-
.info(name) ⇒ Object
Inspect a single bundle without decrypting the payload.
-
.load(name, passphrase: nil) ⇒ Object
Load and decode a bundle.
- .path(name) ⇒ Object
-
.save(name, payload) ⇒ Object
Persist a bundle.
- .validate_name!(name) ⇒ Object
Class Method Details
.all ⇒ Object
Read manifests for all stored bundles. Errors on a single file are surfaced via { error: “…”, path: “…” } rather than aborting the list.
170 171 172 173 174 175 176 |
# File 'lib/browserctl/state.rb', line 170 def self.all return [] unless Dir.exist?(BASE_DIR) Dir[File.join(BASE_DIR, "*#{EXTENSION}")].map do |file| info_for(file) end end |
.delete(name) ⇒ Object
123 124 125 126 |
# File 'lib/browserctl/state.rb', line 123 def self.delete(name) validate_name!(name) FileUtils.rm_f(path(name)) end |
.exist?(name) ⇒ Boolean
81 |
# File 'lib/browserctl/state.rb', line 81 def self.exist?(name) = File.exist?(path(name)) |
.export(name, destination) ⇒ Object
Copies the on-disk .bctl bundle to a transport-addressable destination (file path, s3://bucket/key, op://Vault/Item, or any registered scheme). Bundle bytes are written verbatim — no re-encoding — so the receiving side can verify the manifest/payload exactly as produced.
132 133 134 135 136 137 138 139 140 |
# File 'lib/browserctl/state.rb', line 132 def self.export(name, destination) validate_name!(name) raise Browserctl::Error, "state '#{name}' not found" unless exist?(name) transport, parsed = Transport.for(destination) blob = ::File.binread(path(name)) transport.write(parsed, blob) { name: name, destination: destination, bytes: blob.bytesize } end |
.import(source, name: nil) ⇒ Object
Pulls a bundle from a transport-addressable source and stores it as a local state. Validates the magic header before persisting so we never leave a corrupt bundle in the state directory. ‘name` defaults to the source’s basename without ‘.bctl`.
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/browserctl/state.rb', line 146 def self.import(source, name: nil) transport, parsed = Transport.for(source) blob = transport.read(parsed) raise Bundle::BundleError, "imported blob is not a .bctl bundle" unless blob.start_with?(Bundle::MAGIC) manifest = Bundle.peek_manifest(blob) target_name = name || derive_name(source) || manifest[:name] validate_name!(target_name) FileUtils.mkdir_p(BASE_DIR) ::File.open(path(target_name), "wb", 0o600) { |f| f.write(blob) } { name: target_name, source: source, bytes: blob.bytesize, encrypted: manifest[:encrypted] } end |
.info(name) ⇒ Object
Inspect a single bundle without decrypting the payload.
179 180 181 182 183 184 |
# File 'lib/browserctl/state.rb', line 179 def self.info(name) validate_name!(name) raise Browserctl::Error, "state '#{name}' not found" unless exist?(name) info_for(path(name)) end |
.load(name, passphrase: nil) ⇒ Object
Load and decode a bundle. Returns { manifest:, payload:, encrypted: }.
116 117 118 119 120 121 |
# File 'lib/browserctl/state.rb', line 116 def self.load(name, passphrase: nil) validate_name!(name) raise Browserctl::Error, "state '#{name}' not found" unless exist?(name) Bundle.decode(File.binread(path(name)), passphrase: passphrase) end |
.path(name) ⇒ Object
80 |
# File 'lib/browserctl/state.rb', line 80 def self.path(name) = File.join(BASE_DIR, "#{name}#{EXTENSION}") |
.save(name, payload) ⇒ Object
Persist a bundle. ‘payload` is a `State::Payload` value object carrying cookies, local/session storage, and the manifest extras (origins, flow, flow_version, passphrase).
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/browserctl/state.rb', line 96 def self.save(name, payload) validate_name!(name) FileUtils.mkdir_p(BASE_DIR) bundle_payload = payload.to_bundle_payload manifest = build_manifest( name: name, origins: payload.origins || derive_origins(bundle_payload), flow: payload.flow, flow_version: payload.flow_version, cookies: payload. || [], encrypted: !payload.passphrase.nil? ) blob = Bundle.encode(manifest: manifest, payload: bundle_payload, passphrase: payload.passphrase) File.open(path(name), "wb", 0o600) { |f| f.write(blob) } manifest end |
.validate_name!(name) ⇒ Object
83 84 85 86 87 88 89 90 91 |
# File 'lib/browserctl/state.rb', line 83 def self.validate_name!(name) return if SAFE_NAME.match?(name.to_s) raise Browserctl::Error.new( "invalid state name #{name.inspect} — use letters, digits, _ or - (max 64 chars)", code: Browserctl::Error::Codes::INVALID_STATE_NAME, context: { name: name } ) end |